Щоб було ясніше, Мартін Фаулер

Найцікавіше про розробку програмного забезпечення

"Щоб було ясніше", Мартін Фаулер

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

Атрибути та відображення

Незважаючи на це, використання відображення не полегшує процес модифікації коду. Якщо я спробую використати цю структуру даних, я не зможу одразу визначити, що в ній. Щоб дізнатися, що хтось використовує її для зберігання кількості матеріально залежних осіб (numberOfDependents), мені потрібно прочесати всю систему. Якби відповідну змінну було визначено в класі, то мені було б достатньо одного погляду на код класу, щоб з'ясувати, які поля він підтримує.

Основний принцип ясного коду в тому, що його простіше розуміти (і як наслідок, простіше модифікувати). Як каже Кент Бек, такий код є чітким виразом початкових намірів.

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

Рисунок 1. Використання полів класу (мова Ruby)

Малюнок 2. Використання відображення для зберігання полів (Ruby)

Події та явні виклики

Ми можемо реалізувати це, використовуючи подію, як показано на малюнку 3. Для цього достатньо визначити різні події для модуля попередніх замовлень і будь-який об'єкт, який хочевиконати деякі дії при настанні будь-якої події може визначити для нього відповідний обробник. Цей підхід виглядає привабливим, тому що нам не доведеться вносити зміни до класу Reservation, якщо ми захочемо додати будь-які додаткові дії при скасуванні попереднього замовлення. Інші об'єкти теж можуть додавати обробники подій, тому ви зможете легко нарощувати поведінку у точках їх обробки.

Рисунок 3. Скасування замовлення з використанням подій (мова C#)

Малюнок 4. Явна реакція на відміну замовлення (мова C#)

Я бачив кілька прикладів коду, де дуже інтенсивно використовувалися події. І кожного разу в таких випадках було важко зрозуміти, як поводиться програма при виклику будь-якого методу. Особливо незручно це при налагодженні, тому що деяка поведінка системи з'являється раптом там, де її зовсім не чекаєш.

Я не стверджую, що події взагалі не можна використати. Вони дозволяють збільшувати поведінку класу додатковими процесами, не змінюючи сам клас. Це особливо зручно при роботі з бібліотечними класами, які ви не можете змінити безпосередньо. Події так само цінні тим, що не створюють залежності між класом, що ініціює настання події, і класом, що його обробляє. Якщо ці класи знаходяться в різних пакетах, і ви не хочете робити пакети залежними, відсутність залежності буде особливо корисною. Прикладом може бути зміна вмісту вікна на презентаційному рівні при зміні об'єкта предметної області (моделі). Механізм подій дозволяє зробити це, зберігши винятково важливий поділ між моделлю та її презентацією.

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

Як ви могли помітити, при прийнятті проектувальних рішень ясність коду не є домінуючим принципом. У нашому прикладі не меншу роль грають пакетування та залежності. Проте розробники часто недооцінюють важливість зрозумілості коду. Бувають випадки, коли я хотів би додати залежність, щоб зробити код зрозумілішим, але, як це завжди буває при проектуванні, для кожної конкретної ситуації потрібно враховувати й інші альтернативні варіанти.

Код, поведінка якого визначається даними, та явне успадкування

При використанні одного загального класу вам не потрібно буде визначати нові підкласи при додаванні нових правил для обчислення знижки, якщо, звичайно, поведінка цього загального класу дозволяє обчислити знижку за новою схемою. (Щоб спростити аргументацію, припустімо, що дозволяє). Але чи завжди потрібно використовувати дизайн із одним загальним класом, чи завжди він буде найкращим рішенням? Ні, і з-за міркувань ясності.

Малюнок 5. Явна логіка обчислення знижок (C#)

Рисунок 6. Логіка обчислення знижок, що базується на даних (мова C#)

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

Нескладність поставки нового коду може також впливати на вибір дизайну. Якщо ви легко додаєте в існуючу систему нові підкласи, можна зупинитися на"ясний" варіант дизайну. Якщо ж поява нового коду пов'язані з тривалим і незручним процесом компіляції та складання, необхідно вибирати більш загальне поведінка.

Деякі розробники пропонують комбінувати обидва ці підходи. Наприклад, можна використовувати загальний дизайн, що визначається даними для більшості простих випадків, а спеціальні підкласи - для складних. Мені подобається цей варіант, тому що при такому підході узагальнений дизайн стає набагато простіше, а використання підкласів забезпечує системі необхідну гнучкість.

Ясність не є необхідною умовою проектування, але хитромудрий дизайн буває складно використовувати саме через його неясність. Бувають випадки, коли це виправдано, проте завжди варто подумати і про ясніші альтернативи. Протягом останніх кількох років я набагато частіше віддаю перевагу ясному дизайну, тому що моя думка про те, що є гарним дизайном, дещо змінилася (і я сподіваюся, що в правильному напрямку).