Основні принципи програмування: інтроспективна і рефлексія


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Розповідає Аарон Краус


Часто під час роботи програми нам буває потрібна інформація про дані, наприклад, який у них тип або чи є вони екземпляром класу (у ТОП). Спираючись на ці знання, нам треба проводити над даними деякі операції, або навіть змінювати їх, але необхідного виду даних у нас може і не бути! Якщо Ви нічого не зрозуміли, не засмучуйтеся – ми детально в усьому розберемося. Все, що я тут описав, – це ілюстрація цілей двох можливостей, наявних майже в кожній сучасній мові програмування: інтроспективні та рефлексії.

Інтроспективна

Інтроспективна – це здатність програми досліджувати тип або властивості об’єкта під час роботи програми. Як ми вже згадували, Ви можете поцікавитися, який тип об’єкта, чи є він екземпляром класу. Деякі мови навіть дозволяють дізнатись про ієрархію спадкоємства об’єкта. Можливість інтроспективної є в таких мовах, як Ruby, Java, PHP, Python, C++ і інших. У цілому, інстроспекція – це дуже просте і потужне явище. Ось декілька прикладів використання інстроспекції:

// Java if (obj instanceof Person){ Person p =  (Person) obj;
p.walk ();}
//PHP if ($obj instanceof Person) { // робимо що завгодно}

У Python найпоширенішою формою інтроспективної є використання методу dir для виведення списку атрибутів об’єкту :

# Python class foo (object): def __init__ (self, val): self.x = val def bar (self): return self.x ... dir (foo (5)) => ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__','__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', 'bar', 'x']

У Ruby інтроспективна дуже корисна – зокрема через те, як влаштована сама мова. У ній все є об’єктами – навіть клас – і це призводить до цікавих можливостей в плані спадкоємства і рефлексії (про це нижче). Якщо Ви хочете дізнатися про це більше, раджу прочитати міні-цикл Metaprogramming in Ruby.

Прим. перекл. Також не буде зайвим прочитати нашу статтю, присвячену інтроспективній в Ruby.

Ось декілька простих прикладів інтроспективної з використанням IRB (Interactive Ruby Shell) :

# Ruby $ irbirb (main): 001: 0> A=Class.new=> Airb (main): 002: 0> B=Class.new A=> B
irb (main): 003: 0> a=A.new=> #irb(main):004:0> b=B.new=> #irb(main):005:0> a.instance_of? A=> trueirb (main): 006: 0> b.instance_of? A=> falseirb (main): 007: 0> b.kind_of? A=> true

Ви також можете дізнатися в об’єкта про те, екземпляром якого класу він є, і навіть “порівняти” класи.

# Ruby irb (main): 008: 0> A.instance_of? Class=> trueirb (main): 009: 0> a.class=> Airb (main): 010: 0> a.class.class=> Classirb (main): 011: 0> A > B=> trueirb (main): 012: 0> B <= A=> true

Проте інтроспективна – це не рефлексія, що дозволяє нам використати ключові принципи інтроспективної форми і робити дійсно потужні речі з нашим кодом.

Рефлексія

З тієї ж причини рефлексію простіше реалізувати в інтерпретованих мовах, оскільки коли функції, об’єкти та інші структури даних створюються і викликаються під час роботи програми, використовується якась система розподілу пам’яті. Інтерпретовані мови зазвичай надають таку систему за умовчанням, а для компільованих знадобиться додатковий компілятор та інтерпретатор, який стежить за коректністю рефлексії.

Мені здається, що ми сказали багато про визначення рефлексії, але сенсу все ще мало. Давайте розглянемо приклади коду, наведені нижче (з рефлексією і без), кожен з яких створює об’єкт класу Foo і викликає метод hello.

// ECMAScript - як JavaScript // Без рефлексииnew Foo ().hello () // З рефлексією // припускаємо, що Foo належить thisnew this['Foo']()['hello']()
// чи не предполагаемnew (eval ('Foo')) ()['hello']() // чи взагалі не заморачиваемсяeval ('new Foo ().hello()')
// Java // Без рефлексииFoo foo = new Foo ();foo.hello (); // З рефлексиейObject foo = Class.forName ("complete.classpath.and.Foo").newInstance ();// Альтернатива: Object foo = Foo.class.newInstance ();Method m = foo.getClass ().getDeclaredMethod ("hello", new Class<?>[0]);m.invoke (foo);
# Python # Без рефлексииobj = Foo () obj.hello () # З рефлексиейclass_name = " Foo" method = " hello" obj = globals ()[class_name]()getattr (obj, method) () # З evaleval ("Foo ().hello()")
# Ruby # Без рефлексииobj = Foo.newobj.hello # З рефлексиейclass_name = " Foo" method = :hello
obj = Kernel.const_get (class_name).newobj.send method # З evaleval "Foo.new.hello"

Цей список зовсім не вичерпує можливості рефлексії. Це дуже потужний принцип, який до того ж є звичайною практикою в метапрограмуванні. Проте при використанні рефлексії треба бути дуже уважним. Хоча у неї і є свої переваги, код, що використовує рефлексію, значно менш читаний, він утруднює відладку, а також відкриває двері по-справжньому поганим речами, наприклад, ін’єкції коду через вирази eval.

Eval-вирази

Деякі рефлективні мови надають можливість використання eval-виразів – виразів, які розпізнають значення (зазвичай рядок) як вираз. Такі твердження – це найпотужніший принцип рефлексії і навіть метапрограмування, але також і найнебезпечніший, оскільки вони є загрозою безпеки.

Розглянемо наступний приклад коду на Python, який приймає дані зі стороннього джерела в Мережі (це одна з причин, через яку люди користуються eval-виразами):

session['authenticated'] = Falsedata = get_data () foo = eval (data)

Захист програми буде порушений, якщо хтось передасть у метод get_data () такий рядок:

"session.update (authenticated=True) "

Для безпечного використання eval-тверджень треба істотно обмежувати формат вхідних даних – і звичайно це лише займає зайвий час.

Висновок

Інтроспективна і рефлексія – це дуже потужні інструменти сучасних мов, їх розуміння може дозволити Вам писати кращий код. Зазначимо: інтроспективна – це вивчення атрибутів об’єкта, а рефлексія – це маніпуляція ними. Будьте уважні при використанні рефлексії, оскільки вона може зробити Ваш код нечитаним і вразливим. Чим більше сили, тим більше і відповідальність – ось девіз всього, що пов’язане з метапрогрмуванням.

Переклад статті “Programming Concepts: Type Introspection and Reflection”

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *