Загубени в превода: Design Patterns

Този пост е съставен от две неща – кратък rant за някои особености на българския език и странен паралел с designs patterns (които упорито няма да наричам „шаблони за дизайн“). Първо ще се опитам да ви убедя в загубената кауза да използваме „чуждици“ като „патърн“ (или „патерн“) и след това (за пореден път) ще се опитам да илюстирирам, че „дизайн патърните“ въобще не са нещо лошо, дори в език като Ruby.

Първо, няколко думи за употребата на design patterns. Ако не четете книгата отгоре-отгоре, ще видите, че това са „примери за добър дизайн, чиито идеи могат да се преизползват“, а не „рецепти за решаване на сходни проблеми“. По-важното нещо за преизползване са идеите зад pattern-ите, а не структурата на кода им. Дори имената служат за да навяват асоциации, а не да именоват структурно еднакви парчета код.

Това е изключително важно уточнение, поради най-честата грешка при прилагането на тази книга – преизползват се структурите (интерфейс IObserver и клас AbstractObserver), а не идеите зад тях (индирекция между клас, който прави нещо, и друг, който иска да знае за това).

Из превода

Следват, дефиниции на думите „шаблон“ и „pattern“. Едно речниково значение на „шаблон“ e:

мн. шаблони, (два) шаблона, м.
1 Образец (форма, калъп и др.), по който се изработват множество еднакви изделия. Шаблон за буква.
2 Прен. Само ед. Общоизвестен, изтъркан модел за творческа дейност, който води до еднообразие; липса на оригиналност. Мисли по шаблон.
3 Прен. Шаблонен израз. Речевите шаблони.

Ето и релевантната част от значението на „pattern“ от New Oxford American Dictionary:

pattern |ˈpat(ə)n|
noun
2 a regular and intelligible form or sequence discernible in the way in which something happens or is done: a complicating factor is the change in working patterns.
4 an excellent example for others to follow: he set the pattern for subsequent study.

Забавлява ме как този превод има грешка още в заглавието.

Впрочем, ето някои други възможности за превод, взети от българско-английския речник:

образец, пример, мостра, шаблон, еталон, кройка, диаграма, схема, стил, характер, устройство, строеж, структура, насока, тенденция, особеност

Аз лично харесвам първата – образец. Носи ми най-подходящата асоциация. И въпреки това, не ми харесва, понеже в този контекст „pattern“ е по-скоро жаргонна дума със специфичен смисъл.

Design patterns in Ruby, vol. 3

В кода на сайтовете на курсовете, които водим, имаме клас TaskChecker, който проверява предадените решения. Той прави следните неща:

  1. Намира всички решения, които трябва да пусне.
  2. Пуска тестовете на задачата с всяко решение (прави shell out за целта).
  3. Записва резултатите в базата.

Един от начините да пуснем проверка е с бутона „Провери решенията“ в сайта. Използва се message queue (Sidekiq) за да се изпълни всичко в background-а. Когато той приключи, резултатите са в базата и могат да бъдат разгледани от администратор, преди да бъдат обявени публично. Кода в worker-а е следния:

checker = TaskChecker.new task
checker.run

Банален клас с банален интерфейс.

Втората употреба е от Rake задача. Преподавателите могат да изтеглят решенията при себе си и да ги пуснат в локална версия на системата. Това е удобен начин да се провери дали тестовете са адекватни – понякога се „сгъбват“ с работещи решения на студентите, а друг път откриваме, че липсват проверки за често срещани грешки.

Ще бъде хубаво, ако Rake задачата показва някакво състояние: например, след всяка проверка показва колко теста са минали и колко не са. Кодът е следния:

checker = TaskChecker.new task

checker.on_each_solution do |s|
  puts "##{s.user_id} Passed: #{s.passed_tests} Failed: #{s.failed_tests}"
end

checker.run

оригинала променливата се казва solution, но тук я прекръстих на s за да бъде по-кратко)

Разликата е в извикването на on_each_solution. За незнаещите Ruby, това е като извикване на метод с един аргумент, който е анонимна функция. Последната има един параметър и принтира низ. TaskChecker запазва блока и го извиква след всяка проверка.

Как наричам това? Observer.

Но защо? В крайна сметка, няма еквивалентите на IObservable и IObserver. Няма addObserver, removeObserver и прочее.

Есенцията на този „образец за дизайн“ е индирекцията между класа, който изпълнява задачите, и кода, който го ползва. Тук имаме два клиента – worker-а на Sidekiq и Rake задачата. Първият го ползва единствено заради страничните му ефекти, докато вторият има нужда да знае малко повече и да реагира по подходящ начин (извежда по един ред за всяка проверка). Това е стандартно приложение на observer.

Какви биха били алтернативите?

  • TaskRunner може винаги да принтира резултати. Докато вероятно няма да пречат на никого (логват се някъде от sidekiq), това е мърляво решение.
  • TaskRunner може да приема булев параметър дали принтира резултати. Една идея по-добре, но също мърляво. Логиката за извеждане на резултати в текстова конзола (била тя и един ред) няма работа там. Също, ако ни трябва трети клиент (например dump до CSV файл), няма как да стане.
  • TaskRunner може да приема форматиращ низ. Това ще реши (някак) проблема с конзолата и CSV-то, но става несериозно.
  • Rake задачата повтаря кода и добавя принтиране. Кодът е достатъчно малък, за да може да се copy/paste-не и промени, но това „решение“ също има своите проблеми. Ако част от логика се промени (намиране на решения, изпълняване на проверка, добавяне на резултати в базата) ще трябва да се сетим да обновим и rake задачата. Това лесно се забравя.

Ето, че с малко вдъхновение от книгата може да измислим просто и елегантно решение. Ключовият момент е използването на observer като „pattern“, а не като „шаблон“.

Null Object

Впрочем, има още един „дизайн образец“ в TaskChecker – видяхте ли го? Намира се в конструктора. Полето @on_each_solution се инициализира с lambda { |_| }, което е функция на един аргумент, която не прави абсолютно нищо. Така run няма нужда от if, с който да проверява дали @on_each_solution е nil.

Това напомня на друг популярен pattern – Null Object. Това е хубав начин да се справим със специални случаи, без да експлозия от if-ове.

Не мисля, че null object-а е наложителен в този код – би бил достатъчно прост и с един if в run. Но не пречи.

В заключение

Design Patterns не е книга с рецепти, а каталог с идеи. Те са широко приложими, особено ако се вземе предвид как се пренасят в различни езици.

А „шаблони за дизайн“ е лош превод.

3 thoughts on “Загубени в превода: Design Patterns

  1. Браво, Стефане! „Образец“ е изключително удачен превод на pattern. В тазвечершната лекция обектно-ориентираното програмиране ще взаимствам щедро от теб (=

  2. Скромно предложение за допълнение: В поста говориш за „книгата“, метни един параграф увод за недоразбралите, за коя книга говориш (предполагам ‘Design Patterns: Elements of Reusable Object-Oriented Software’) и за кой превод (има я на български?).

  3. В повечето случаи в които стане дума за design patterns, разговорът бързо отива към крайни мнения и доволно количество догматизъм. Всъщност от толкова много време не бях чувал разумно изказване по тази тема, че трябва да призная че бях силно (и приятно) изненадан от поста ти. Евала!

Вашият коментар

Вашият имейл адрес няма да бъде публикуван.