За да бъде ясно, Мартин Фаулър

Най-интересното в разработката на софтуер

„За да бъде по-ясно“ от Мартин Фаулър

Именно желанието за вариативност е причината дизайнът на системата да показва ясно какво и как прави. В крайна сметка е доста трудно да правите промени в програма, ако дори не разбирате как работи. Интересна последица от това е случаят, когато разработчиците създават специален дизайн, който лесно се променя, което в същото време затруднява разбирането на кода. Тогава, разбира се, ефектът е точно обратният.

Атрибути и съпоставяния

Въпреки това използването на картографиране не улеснява модифицирането на кода. Ако се опитам да използвам тази структура от данни, не мога веднага да определя какво има в нея. За да разбера, че някой го използва, за да съхранява броя на финансово зависимите лица (numberOfDependents), трябва да претърся цялата система. Ако съответната променлива беше дефинирана в класа, тогава един поглед към кода на класа би бил достатъчен, за да разбера кои полета поддържа.

Основният принцип на ясния код е, че той е по-лесен за разбиране (и в резултат на това по-лесен за модифициране). Както казва Кент Бек, такъв код е ясен израз на първоначалното намерение.

Нашият пример за картографиране е много малък, но горният принцип е верен при проектирането на софтуерни системи от всякакъв мащаб.

Фигура 1. Използване на полета на клас (език Ruby)

Фигура 2. Използване на картографиране за съхраняване на полета (език Ruby)

Събития и изрични обаждания

Можем да приложим това, като използваме събитие, както е показано на фигура 3. За да направите това, достатъчно е да дефинирате различни събития за модула за предварителни поръчки и всеки обект, който искаизпълни някакво действие при възникване на събитие, може да дефинира подходящ манипулатор за него. Този подход изглежда привлекателен, защото няма да се налага да правим промени в класа Reservation, ако искаме да добавим допълнителни действия, когато анулираме резервация. Други обекти също могат да добавят манипулатори на събития, така че можете лесно да добавите поведение в точките, където се обработват.

Фигура 3. Анулиране на поръчка с помощта на събития (C#)

Фигура 4. Явен отговор на анулиране на поръчка (език C#)

Виждал съм няколко примера за код, които използват силно събития. И всеки път в такива случаи беше трудно да се разбере как се държи програмата, когато се извика метод. Това е особено неудобно при отстраняване на грешки, защото някакво поведение на системата внезапно се появява там, където изобщо не го очаквате.

Не казвам, че събитията изобщо не трябва да се използват. Те ви позволяват да разширите поведението на клас с допълнителни действия, без да променяте самия клас. Това е особено полезно, когато работите с библиотечни класове, които не можете да промените директно. Събитията също са ценни с това, че не създават зависимост между класа, който задейства събитието, и класа, който го обработва. Ако тези класове са в различни пакети и не искате да правите зависимости на пакетите, липсата на зависимост е особено полезна. Пример за това е промяната в съдържанието на прозореца на презентационно ниво при промяна на обекта на предметната област (модела). Механизмът за събития ви позволява да направите това, като същевременно поддържате изключително важното разделение между модела и неговото представяне.

И двете причини могат да доведат до използването на събития, но при липсата на такива причини е много по-важно да направите кода разбираем. Поне,Не бих използвал събития за комуникация между класове, които вече знаят за съществуването на другия.

Както може би сте забелязали, яснотата на кода не винаги е доминиращият принцип при вземането на дизайнерски решения. В нашия пример опаковката и зависимостите играят също толкова важна роля. Разработчиците обаче често подценяват значението на яснотата на кода. Има моменти, когато бих предпочел да добавя зависимост, за да направя кода по-разбираем, но както винаги се случва с дизайна, трябва да се вземат предвид други алтернативи за всяка конкретна ситуация.

Код, управляван от данни, и явно наследяване

Ако използвате един общ клас, няма да е необходимо да дефинирате нови подкласове, когато добавяте нови правила за изчисляване на отстъпката, освен ако, разбира се, поведението на този общ клас ви позволява да изчислите отстъпката според новата схема. (За да опростим аргумента, нека приемем, че позволява). Но винаги ли е необходимо да се използва дизайн с един общ клас, винаги ли е най-доброто решение? Не, и отново от съображения за яснота.

Фигура 5. Ясна логика за изчисляване на отстъпки (език C#)

Фигура 6. Логика за изчисляване на отстъпки въз основа на данни (език C#)

Опцията за общ клас е добра, когато имате работа с десетки правила за отстъпка. В тези случаи количеството допълнителен код за подкласовете ще бъде проблем, но количеството допълнителни данни може да не е проблем. Понякога една добре подбрана и управлявана от данни абстракция може да позволи логиката да бъде компресирана в малка и лесна за поддръжка част от кода.

Лесното доставяне на нов код също може да повлияе на избора на дизайн. Ако можете лесно да добавяте нови подкласове към съществуваща система, тогава можете да спрете на"изчистен" дизайн. Ако появата на нов код е свързана с дълъг и неудобен процес на компилиране и асемблиране, тогава трябва да се избере по-общо поведение.

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

Яснотата не е предпоставка за дизайна, но неясният дизайн може да бъде труден за използване именно поради неговата неяснота. Има моменти, когато това е оправдано, но винаги си струва да обмислите по-ясни алтернативи. През последните няколко години предпочитах ясния дизайн много по-често, тъй като мнението ми за това какво представлява добър дизайн се промени донякъде (и надявам се в правилната посока).