Целта на първата статия, „„Най-добра практика за разработка на софтуер — №1, не се повтаряйте““, беше да ви запознае с проблемите, често възникващи в резултат на дублиране на изходния код. Втората статия, „„Най-добри практики за разработка на софтуер — #2 Loose Coupling““, ви предупреди за сложния характер на тясното свързване и проблемите, които могат да ви засегнат дълго след като разрешите тясно свързване във вашия изходен код. Тази статия ще бъде за поддържане на вашия изходен код чист и прост, така че вашите колеги да могат да се насладят на четенето му, без да искат да ви изтрият от повърхността на земята. Техниките и съветите, представени в тази статия, ще ви помогнат, въз основа на моя личен опит, да бъдете по-ефективни и уверени в програмирането си и ще ви направят по-щастливи, докато вършите ежедневната си работа като софтуерен разработчик.

Преди да започнем, бих искал да спомена, че абстракциите за гъвкавост и удобство обикновено идват с леки разходи за производителност. Следователно, в някои крайни случаи може да не е опция да инвестирате в тях. Въпреки това, в повечето случаи, особено в корпоративните приложения, ползите от тях надвишават разходите за влошаване на производителността. Примерите в статията ще бъдат представени на езика .NET C#; принципите обаче се прилагат за повечето широко използвани езици за програмиране.

Принцип на единната отговорност

Вярвам, че никой не обича да се поти, докато чете сложни конструкции на изходния код. Няма нищо забавно в необходимостта да абсорбирате компонент с чудовищен размер, само за да можете да го разберете. Обзалагам се, че много от вас са преживели тази ситуация, когато се канеха да отворят този стар всемогъщ бог клас с хиляда реда и вместо това отидоха да си сварят кафе, за да отложат този момент. Със сигурност има случаи, в които е удобно да запазите куп сегменти от изходния код заедно. Въпреки това, според моя опит, през повечето време не е така. И в повечето сценарии нарушението на принципа на единичната отговорност (SRP) е основната причина. SRP изисква всеки кодов сегмент (модул, клас, метод и т.н.) да има само една отговорност, или с други думи, само една причина за промяна. Чрез спазване на принципа вашият код естествено ще се сегментира на блокове, които могат да бъдат изолирани за по-добра разбираемост и повторно използване. Нека разгледаме един пример.

Както можете да видите, има много неща, които се случват в метода Process, като проверка на входа, извличане на данни чрез HTTP, съхранение на данни в машината на базата данни и публикуване на съобщения за докладване чрез AWS SNS. Това е доста голяма отговорност за единичния метод. Ако кодовият фрагмент включва целия изходен код, необходим в сценария от реалния свят, методът може лесно да бъде дълъг около 200 реда. Сега, ако отстранявате грешки в приложение и се интересувате само от докладващата част от работния поток, все още трябва да обхождате целия изходен код на метода. Освен това, освен ако не е както във фрагмента по-горе, кодовите фрагменти на различни „отговорности“ обикновено са смесени, така че трябва да се съсредоточите върху цялото тяло на метода и да категоризирате кой кодов сегмент принадлежи към конкретна отговорност. Като средство за защита, нека се опитаме да разделим индивидуалните отговорности на изолирани сегменти.

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

Фрагментът по-горе свободно свързва кода чрез разделяне на отделните отговорности в специални интерфейси със зависимости, осигурени чрез композиция. Вече е възможно да се използва повторно ISerialCodeValidator на различни места, ако е необходимо, или да се замени основната инфраструктура IReportingService, без да се променя изпълнението на самото MyService. Също толкова важно, възможността за тестване на единици на кода също е по-добра, тъй като кодът е сегментиран на по-малки блокове, така че като цяло трябва да се подигравате с по-малко зависимости и да се съсредоточите върху тестването на една функционалност за клас. Можем да видим специфичен модел, който често ще разпознавате, докато пишете своя изходен код. Има клас оркестратор, който диктува потока от действия чрез изпълнение на методи на неговите зависимости. Всяка зависимост може да действа или като оркестратор, или като изпълнителкато изпълнява своята отговорност без допълнителни зависимости.

Промяната на сложността

Какво се случва със сложността, когато се придържаме към SRP? Не изчезва. По-скоро се трансформира в различен тип сложност. Вместо да четете сложни класови структури, съдържащи логика на смесена отговорност, вие ще четете море от микрокласове, свързани чрез зависимости. Поражда свои собствени видове трудности. Мисля, че вече сте чували, че една от най-предизвикателните части на програмирането е „как да наименувате неща“ и „къде да поставите неща“. Това е особено вярно, когато се придържате към SRP, защото създавате много класове и модели с едно предназначение. Вярвам обаче, че ползите от принципа надвишават негативите в голяма степен.

Принцип отворено-затворено

Тясно свързан с SRP е принципът отворено-затворено (OCP). Принципът гласи, че кодът, който пишете, трябва да бъде отворен за разширения, докато е затворен за модификации. С други думи, трябва да можем да добавяме функционалност към кода, без да е необходимо да променяме съществуващия код. Какво трябва да означава това? Нека го покажем в примера.

Придържа ли се горният фрагмент към SRP? Предполагам, че можем да кажем предимно „да“. Фокусира се върху една задача, да потвърди имейла. В същото време можем да кажем, че той извършва три различни типа валидиране. Придържа ли се към OCP? Не е, защото ако ще добавим друг тип валидиране на имейл, най-вероятно ще променим изходния код на EmailValidator. Освен това може да се окаже предизвикателство да се тества модулно класа, тъй като не е възможно да се изолират различни типове валидиране и да се тестват отделно.

А сега? Кодът по-горе придържа ли се към SRP? Бих казал да, тъй като всеки валидатор се фокусира върху един тип валидиране, а EmailValidator действа като оркестратор. Възможността за единично тестване на кода също е увеличена, тъй като вече можем да тестваме всеки тип валидиране поотделно. Кодът спазва ли OCP? Не съвсем, тъй като разширението на имейл валидирането с нов тип валидиране ще изисква промяна на изходния код EmailValidator. Нека представим още една промяна в дизайна.

Кодовият фрагмент по-горе изпълнява OCP. Добавянето на нов тип валидиране на имейл не изисква промяна в нито един от класовете. Разбира се, промяната ще се изисква в конфигурацията на инструмента за инверсия на контролата. Надявам се разликата от първоначалния код да е видима. Започнахме от състояние, в което се страхувахме от нови типове валидиране поради необходимостта да модифицираме част от изходния код и да коригираме модулните тестове. В крайна сметка се оказахме напълно подготвени за новите изисквания за валидиране и ги прегърнахме с благодат!

Може да възразите, че първоначалната форма на кода изглежда по-проста от крайната форма. И може да е вярно за определени сценарии. Винаги трябва да обмисляте ползите, получени от следването на OCP спрямо допълнителното ниво на абстракция, въведено от него. Имайте предвид обаче повишаването на възможността за тестване на модула и факта, че писането на нов код обикновено е по-безопасно, по-бързо и по-забавно от пренаписването на съществуващ код. Ползите са още по-забележителни, когато откриете възможностите на приложението на OCP при по-сложни задачи. За мен е много възнаграждаващо, когато проектирам компонента по начин, който да изпълни изискванията с поддръжка на разширения, които естествено се вписват в системата. И накрая, ако погледнете внимателно окончателната форма на нашите компоненти за валидиране на имейли, може да видите, че потокът за валидиране е универсален и не е необходимо да се прилага непременно само за валидирането на имейли. С интелигентното използване на генеричните типове вие ​​лесно ще можете да напишете компонент на общ механизъм за валидиране, който може да се използва повторно за всички различни видове валидации, съставени от множество типове валидации. Прилагането на OCP често ви помага да разкриете тези монадични алгоритми, които ще ви помогнат да проектирате своя код по многократно използваем и декларативен начин.

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

Ние сме ACTUM Digital и това парче е написано от Milan Gatyas, .NET технически ръководител на Apollo Division. Чувствайте се свободни да се свържете.