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

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

По-долу има 23 ръководства, които да ви помогнат да пишете по-четлив код, това е дълга статия, така че можете да преминете към части, които ви интересуват:

  1. Идентифицирайте, че имате проблем, преди да създадете решението.
  2. Изберете правилния инструмент за работата.
  3. Простотата е цар.
  4. Вашите функции, класове и компоненти трябва да имат добре дефинирана цел.
  5. Наименуването е трудно, но е важно.
  6. Не дублирайте кода.
  7. Премахнете мъртвия код, не го оставяйте коментиран.
  8. Константните стойности трябва да бъдат в статични константи или преброявания.
  9. Предпочитайте вътрешни функции пред персонализирани решения.
  10. Използвайте специфични за езика указания.
  11. Избягвайте създаването на множество блокове код, вложени един в друг.
  12. Не става въпрос за най-малкия брой редове.
  13. Научете шаблони за проектиране и кога да не ги използвате.
  14. Разделете вашите класове на притежатели на данни и манипулатори на данни.
  15. Коригирайте проблемите в корените им.
  16. Скрит капан на абстракциите.
  17. Правилата на света не са правилата на вашето приложение.
  18. Въведете вашите променливи, ако можете, дори и да не е нужно.
  19. Пишете тестове.
  20. Използвайте инструменти за анализ на статичен код.
  21. Прегледи на човешкия код.
  22. Коментари.
  23. Документация.

1. Идентифицирайте, че имате проблем, преди да създадете решението.

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

Вашият код е потенциален проблем. Дори красивата. Единственият момент, когато част от кода вече не е проблем, е когато даден проект е завършен и мъртъв — вече не се поддържа. Защо? Защото някой ще трябва да го прочете по време на жизнения цикъл на проекта, да го разбере, да го поправи, да го разшири или дори да премахне функцията, която предоставя изцяло.

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

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

Има и други видове проблеми. Да приемем, че трябва да внедрите филтрируем списък от записи. Вие съхранявате данните си в базата данни, но връзките между различните записи са сложни. След като анализирате как клиентът иска данните да бъдат филтрирани, откривате, че поради дизайна на базата данни ще трябва да отделите около 20 часа за изграждане на сложни SQL заявки с множество съединения и вътрешни заявки. Защо не обясните, че има различно решение, което ще отнеме 1 час, но ще пропусне част от функцията? Може да се окаже, че допълнителната функция не струва толкова много от вашето време, което означава парична цена.

2. Изберете правилния инструмент за работата.

Този лъскав език със сребърен куршум, тази рамка, която обичате безусловно, или нова машина за бази данни може да се окаже, че не е правилният инструмент за проблема, пред който сте изправени. Не избирайте инструменти, за които току-що сте чули, че са страхотни за всичко в сериозен проект. Това е рецепта за катастрофа. Ако вашите данни се нуждаят от връзки, изборът на MongoDB само за да научите ще свърши зле. Знаете, че можете да го направите, но често ще ви е необходимо заобиколно решение, което ще произведе допълнителен код, осигуряващ неоптимални решения. И разбира се, можете да ударите гвоздея дори с дървена дъска, но бързо търсене в Google може да ви насочи към чук. Може би след последната ви проверка има AI, който може автоматично да го направи вместо вас.

3. Простотата е цар.

Може би сте чували израза „„преждевременната оптимизация е коренът на всяко зло““. Съдържа частична истина. Трябва да предпочитате прости решения, освен ако не сте уверени, че няма да работят. Не просто да вярвате, че няма да работи, но вече сте го изпробвали или изчислили предварително и сте сигурни, че няма да работи. Изборът на по-сложно решение по каквато и да е причина, било то скорост на изпълнение, липса на RAM, разширяемост, липса на зависимости или други причини, може да повлияе силно на четимостта на кода. Не усложнявайте нещата, освен ако не се налага. Изключение от това би било, ако знаете по-ефективно решение и знаете, че неговото внедряване няма да повлияе на четливостта или вашите изисквания за време.

На същата бележка, не е необходимо да използвате всички нови функции на вашия език, ако това не е от полза за вас и вашия екип. Ново не означава по-добро. Ако не сте сигурни, върнете се към първата точка и помислете какъв проблем се опитвате да разрешите, преди да преработите. Това, че Javascript има твърде много нови начини за писане на израз за цикъл for, не прави циклите for остарели, ако имате нужда от тази индексна променлива.

4. Вашите функции, класове и компоненти трябва да имат добре дефинирана цел.

Знаете ли принципите на SOLID? Открих, че те са доста добри за проектиране на общи библиотеки, но въпреки че го използвах няколко пъти и видях няколко имплементации в работни проекти, мисля, че правилата са малко объркващи и сложни.

Разделете кода си на функции, всяка от които прави едно нещо. Например, нека помислим как бихме приложили бутон. Button може да бъде клас, който групира всички функции на бутон. Можете да приложите бутона с една функция за изчертаването му на екрана, друга функция, за да го маркирате при преместване на мишката, още една, която да бъде извикана при щракване върху бутона и още една, за да анимирате бутона при щракване. Можете да го разделите още повече. Ако трябва да изчислите правоъгълната позиция на бутон въз основа на разделителната способност на екрана, не го правете във функцията за рисуване. Приложете го в различен клас, тъй като може да се използва от други GUI елементи и просто го използвайте, когато рисувате бутона.

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

Помислете за примери по-долу, които правят същото нещо, кой информира какво прави по-бързо?

// C++ 
if (currentDistance < radius2) { 
    // This is sight of a player 
    if (!isLight) { 
        // If lighting of the tile is about 30% (so sight in darkness is worse) or distance from player is 1, tile should be visible. 
        if (hasInfravision || map.getLight(mapPosition) > 0.29f || ASEngine::vmath::distance(center, mapPosition) == 1) {
            map.toggleVisible(true, mapPosition); 
        } 
    } 
    // This is for light calculations 
    else { 
        ASEngine::ivec3 region = World::inst().map.currentPosition;
        ASEngine::ivec2 pos = mapPosition; 
        if (mapPosition.x > 63) { 
            pos.x -= 64; 
            region.x += 1; 
        } else if (mapPosition.x < 0) { 
            pos.x += 64; 
            region.x -= 1; 
        } if (mapPosition.y > 63) { 
            pos.y -= 64; 
            region.y += 1; 
        } else if (mapPosition.y < 0) { 
            pos.y += 64; 
            region.y -= 1; 
        }
        map.changeLight(pos, region, 1.0f - static_cast(currentDistance) / static_cast(radius2)); 
    }
}
// C++
if (currentDistance < radius2) {
    // This is sight of a player
    if (!isLight) {
        this->markVisibleTile(hasInfravision, map, center, mapPosition);
    }
    // This is for light calculations
    else {
        ASEngine::ivec3 region = World::inst().map.currentPosition;
        ASEngine::ivec2 pos = map.getRelativePosition(mapPosition, region);
        map.changeLight(pos, region, 1.0f - static_cast(currentDistance) / static_cast(radius2));
    }
}

5. Наименуването е трудно, но е важно.

Имената на променливите и функциите трябва да са различни и да дават обща представа какво правят. Важното при наименуването е, че трябва да описва какво прави с вашия екип, така че трябва да отговаря на конвенциите, избрани в проекта. Дори и да не сте съгласни с тях. Ако всяка заявка за запис в базата данни започва с думата „намери“, като „findUser“, тогава вашият екип може да се обърка, ако дойдете на проекта и назовете функцията на вашата база данни „getUserProfile“, защото това е, с което сте свикнали. Опитайте се да групирате именуване, когато е възможно, например, ако имате много класове за проверка на входа, поставянето на „Валидатор“ като суфикс за името може бързо да предостави информация каква е целта на класа.

Изберете и се придържайте към тип каса според стандартите. Става наистина объркващо да се четат camelCase, snake_case, kebab-case и beer🍻case, използвани в различни файлове на един и същи проект.

6. Не дублирайте кода.

Вече установихме, че кодът е проблем, така че защо да дублирате проблемите си, за да спестите няколко минути? Наистина няма смисъл. Може да си мислите, че решавате нещо бързо, като просто копирате и поставяте, но ако смятате, че трябва да копирате повече от 2 реда код, опитайте се да приемете идеята, че може да пропускате възможност за по-добро решение. Може би обща функция или цикъл?

7. Премахнете мъртъв код, не го оставяйте коментиран.

Коментираният код е объркващ. Някой премахна ли го временно? Важно ли е? Кога е коментирано? Мъртво е, избавете го от мизерията му. Просто го махнете. Разбирам, че се колебаете да премахнете кода, защото нещата могат да се объркат и искате просто да го разкоментирате. Може дори да сте много привързани към него, тъй като сте изразходвали времето и енергията си, за да го измислите. Или може би смятате, че може да е необходимо „скоро“. Решението на всички тези проблеми е софтуерът за контрол на версиите. Просто използвайте git history, за да извлечете кода, ако някога ви потрябва. Почистете след себе си!

8. Константните стойности трябва да бъдат в статични константи или преброявания.

Използвате ли низове или цели числа, за да дефинирате типове обекти? Например потребител може да има роля „администратор“ или „гост“. Как ще проверите дали потребителят има роля „администратор“?

if ($user->role == "admin") { 
    // user is admin
}

Това не е страхотно. Първо, ако името „admin“ се промени, ще трябва да го промените в цялото си приложение. Може да кажете, че това се случва рядко и модерните IDE не го правят толкова трудно да го замените. Вярно е. Другата причина е липсата на автоматично довършване и поради това възникват проблеми с правописни грешки. Те могат да бъдат доста неприятни за отстраняване на грешки.

Като дефинирате глобални константи или enum в зависимост от езика, можете да се възползвате от автоматичното довършване и да промените стойността на едно място, ако някога се наложи. Дори не е нужно да си спомняте каква стойност е скрита зад константата, просто оставяте вашата магия за автоматично довършване на IDE да ви помогне.

// PHP
const ROLE_ADMIN = "admin";
 
if ($user->role == ROLE_ADMIN) { 
    // user is admin
}
// C++
enum class Role { GUEST, ADMIN }; // It's possible to map these enums to strings, but it's not needed.
 
if (user.role == Role.ADMIN) { 
    // user is admin
}

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

9. Предпочитайте вътрешните функции пред персонализираните решения.

Ако вашият език или рамка, която сте избрали за вашия проект, ви предоставя решение на вашия проблем, използвайте го. Всеки може бързо да търси в Google какво прави дадена функция, дори ако не се използва често. Вероятно ще отнеме повече време, за да разберете вашето персонализирано решение. Ако намерите част от кода, който прави същото като вътрешна функция, просто преработете бързо, не го оставяйте така. Премахнатият код вече не е проблем, така че изтриването на код е страхотно!

10. Използвайте специфични за езика указания.

Ако пишете на PHP, трябва да се запознаете с PSR. За Javascript има прилични насоки от Airbnb. За C++ има насоки от google или основни насоки от Bjarne Stroustrup, създателят на C++. Други езици може да имат свои собствени насоки за код за качество или дори можете да измислите свои собствени стандарти за вашия екип. Важната част е „да се наложи използването на избраната насока“ за проекта, така че да има единна визия за това как трябва да бъде разработен. Предотвратява много проблеми, които идват от различни хора със собствен уникален опит, правейки това, с което са свикнали.

11. Избягвайте създаването на множество блокове код, вложени един в друг.

Просто сравнете тези два блока код:

void ProgressEffects::progressPoison(Entity entity, std::shared_ptr<Effects> effects)
 {
     float currentTime = DayNightCycle::inst().getCurrentTime();
     if (effects->lastPoisonTick > 0.0f && currentTime > effects->lastPoisonTick + 1.0f) {
         if (effects->poison.second > currentTime) {
             std::shared_ptr<Equipment> eq = nullptr;
             int poisonResitance = 0;
             if (this->manager.entityHasComponent(entity, ComponentType::EQUIPMENT)) {
                 eq = this->manager.getComponent<Equipment>(entity);
                 for (size_t i = 0; i < EQUIP_SLOT_NUM; i++) {
                     if (eq->wearing[i] != invalidEntity && this->manager.entityHasComponent(eq->wearing[i], ComponentType::ARMOR)) {
                         std::shared_ptr<Armor> armor = this->manager.getComponent<Armor>(eq->wearing[i]);
                         poisonResitance += armor->poison;
                     }
                 }
             }
             int damage = effects->poison.first - poisonResitance;
             if (damage < 1) damage = 1;
             std::shared_ptr<Health> health = this->manager.getComponent<Health>(entity);
             health->health -= damage;
         } else {
             effects->poison.second = -1.0f;
         }
     }
 }
void ProgressEffects::progressPoison(Entity entity, std::shared_ptr effects)
 {
     float currentTime = DayNightCycle::inst().getCurrentTime();
     if (effects->lastPoisonTick < 0.0f || currentTime < effects->lastPoisonTick + 1.0f) return;
     if (effects->poison.second <= currentTime) {
         effects->poison.second = -1.0f;
         return;
     }
     
     int poisonResitance = this->calculatePoisonResistance(entity);
     int damage = effects->poison.first - poisonResitance;
     if (damage < 1) damage = 1;
     std::shared_ptr health = this->manager.getComponent(entity);
     health->health -= damage;
 }

Второто е много по-лесно за четене, нали? Ако такова решение е налично, опитайте се да избегнете влагането на блокове от ifs и цикли един в друг. Често срещан трик е да обърнете командата if и да се върнете от функцията, преди да преминете към блока от код, както в примера по-горе.

12. Не става въпрос за най-малкия брой редове.

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

Някои езици предлагат възможност за писане на кратки изрази if, като например:

$variable == $x ? $y : $z; // if ($variable == x) { $result = $y; } else { $result = $z; }

Може да бъде чудесен избор, но също така лесно може да се прекали:

$variable == $x ? ($x == $y ? array_merge($x, $y, $z) : $x) : $y; // What is this heresy?!

Трябва да е по-лесно за хващане след разделяне.

$result = $y;
if ($variable == $x && $x == $y) $result = array_merge($x, $y, $z);
else if ($variable == $x) $result = $x;

Тези 3 реда заемат повече място на вашия екран, но отнема по-малко време, за да се анализира какво се случва с данните.

13. Научете шаблони за проектиране и кога да не ги използвате.

Има много различни „модели на проектиране“, които често се избират за решаване на проблеми с кодирането. Това, което трябва да имате предвид е, че въпреки че тези модели решават специфични проблеми в приложението, тяхната полезност може да бъде повлияна от много различни фактори като размера на проекта, броя на хората, работещи по него, времеви (разходни) ограничения или изисквана сложност на разтвора. Някои шаблони са наречени антимодели, като модела Singleton, защото въпреки че предоставят някои решения, те също въвеждат проблеми в определени случаи.

Просто се уверете, че разбирате цената на внедряването по отношение на сложността, която ще бъде въведена, преди да изберете модел на проектиране за вашето конкретно решение. Може да не се нуждаете от модела Observer, за да комуникирате между компоненти в проста система, може би няколко булеви стойности ще създадат лесно за следване решение? По-оправдано е да отделите време за внедряване на избран дизайн модел в по-големи, по-сложни приложения.

14. Разделете вашите класове на притежатели на данни и манипулатори на данни.

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

Много добър пример е в архитектурния модел Entity Component System, където компонентите само държат данните, а системите ги манипулират и обработват. Друг случай на използване би бил шаблонът за проектиране на Repository, внедрен за комуникация с външна база данни, където класът „Model“ представлява данни от базата данни в специфични за езика структури, а класът „Repository“ синхронизира данните с база данни, или запазвайки промените в Моделирайте или ги вземете.

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

Какво ще кажете за примера „Entity Component System“? Ако трябва да внедрите системи, които ще обработват използването на умение, възпроизвеждане на анимация, звук, нанасяне на щети и т.н. Не е необходимо да знаете как е задействано умението. Няма значение дали AI скриптове са инициирали умението при определени условия или играчът е използвал клавишна комбинация, за да го активира. Единственото нещо, от което се нуждаете, е да разпознаете, че данните в компонента са променени, като посочите коя способност трябва да бъде обработена.

15. Коригирайте проблемите в корените им.

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

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

16. Скрит капан на абстракциите.

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

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

Когато нещо се обърка и това не е добре документиран проблем, някой ще има много трудности да разбере всички идеи с общо предназначение, които се опитват да разрешат много повече от необходимото. Ако можем да си го позволим, наистина не би трябвало да искаме да крием подробности за изпълнението. Поддържането на контрол върху кодовата база позволява повече гъвкавост. Не пишете общо решение на прости проблеми само защото смятате, че то „може“ да бъде разширено в бъдеще. Рядко е и може да се пренапише, когато е необходимо.

Да разгледаме един пример. Ако можете да създадете клас, който може да импортира данни от CSV файл и да го опакова в база данни в 10–15 реда четим код, защо да си правите труда да правите 2 класа и да обобщавате решението, така че да може потенциално да бъде разширено до импортиране от XLS или XML в бъдеще, когато дори нямате намек, че това ще е необходимо за вашето приложение? Защо да изтегляте външна библиотека от 5k реда код, които не са ви необходими, за да разрешите този проблем?

Рядко има необходимост от обобщаване на мястото за съхранение на вашите данни. Колко пъти в кариерата си сменихте двигателя на базата данни? През последните 10 години се натъкнах на проблем, който веднъж беше решен по този начин. Създаването на абстрактни решения е скъпо и много често ненужно, освен ако не създавате библиотека, която трябва да обслужва огромно разнообразие от проекти наведнъж.

За разлика от това, когато знаете със сигурност, че трябва да разрешите импортиране от XLS и CSV веднага, тогава общото решение може да бъде напълно жизнеспособен избор. Освен това наистина не е голяма работа, ако напишете общо решение по-късно, когато изискванията на вашето приложение се променят. Ще бъде много по-лесно за някой просто да има просто и просто решение, когато трябва да го замени.

17. Правилата на света не са правилата на вашето приложение.

Имах интересен аргумент относно моделирането на „реалния свят“ при внедряването на OOP парадигмата в приложение. Да кажем, че трябва да обработваме големи данни за рекламна система. Имаме 2 вида регистрационни съобщения. Първо е информация за излъчване на реклама, която съдържа някои данни. Вторият дневник информира, че някой човек е кликнал върху рекламата, която съдържа точно същите данни като емисиите и няколко допълнителни полета. Регистърът на емисиите няма данни, които едно кликване не съдържа.

В реалния свят може да считаме и двете действия, гледането и щракването върху реклама, за отделни, но подобни. Така че чрез моделиране на реалния свят бихме могли да създадем базов клас „Log“, който да разширим до класове „ClickLog“ и „EmissionLog“, както по-долу:

struct Log {
    int x;
    int y;
    int z;
}
struct EmissionLog : public Log {}
struct ClickLog : public Log {
    float q;
}

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

В нашето приложение, различно от реалния свят, нашият ClickLog е разширение на EmissionLog. Може да се обработва по същия начин, като се използват същите класове, които работят с EmissionLogs. Ако разширите щракванията от регистрационните файлове на емисиите, вие информирате колегите си, че всичко, което може да се случи с емисиите, може да се случи с кликванията, без да е необходимо те да знаят за всички възможни процесори на регистрационни файлове в приложението.

struct EmissionLog {
    int x;
    int y;
    int z;
}
struct ClickLog : public EmissionLog {
    float q;
}

18. Въведете вашите променливи, ако можете, дори и да не е нужно.

Можете да пропуснете този, ако пишете само на статично въведени езици. В динамично въведени езици като PHP или Javascript може да бъде много трудно да се разбере какво трябва да прави дадена част от кода, без да се изхвърля съдържанието на променливите. По същата причина кодът може да бъде много непредвидим, когато една променлива може да бъде обект, масив или нула в зависимост от някои условия. Позволете възможно най-малкото количество типове на вашите променливи във вашите функционални параметри. Решенията са налични. PHP може да има въведени аргументи и връщани типове от версия 7 и можете да изберете Typescript вместо чист Javascript. Помага за четливостта на кода и предотвратява тъпи грешки.

Ако не е нужно, не позволявайте и нули. Null е мерзост. Трябва изрично да се провери за съществуване, за да се избегнат фатални грешки, които изискват ненужен код. Нещата са още по-ужасни в Javascript, тъй като той е null и недефиниран. Маркирайте променливи, които могат да бъдат null, така че да информирате колегите си:

// PHP >= 7.1
function get(?int count): array { 
    //... 
}
// Typescript
interface IUser = {
    name?: string; // name field might not be available
    type: number;
}

19. Пишете тестове.

С годините минават и успяваме да избегнем изгарянето, подобряваме се до точката, в която можем да начертаем дори сложни функции в ума си и да ги внедрим, без да проверяваме дали кодът ни работи, докато първата чернова не бъде напълно внедрена. В този момент може да се почувствате като загуба на време за писане в „TDD цикли“, тъй като е малко по-бавно да проверявате всяко нещо, преди да го напишете. Добра практика е да пишете интеграционни тестове, които гарантират, че цялата ви функция работи според изискванията, просто защото вероятно ще оставите някои малки грешки и можете да стартирате проверката за милисекунди.

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

20. Използвайте инструменти за анализ на статичен код.

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

Солидни избори за PHP:

Javascript:

  • JsHint / JsLint — откриване на грешки и потенциални проблеми, може да се интегрира с IDE за анализ в реално време.
  • Plato — инструмент за визуализация на изходния код и сложност.

C++:

  • Cppcheck — открива грешки и недефинирано поведение.
  • OClint — подобрява качеството на кода.

Многоезична поддръжка:

  • pmd — детектор на бъркотия.

21. Прегледи на човешкия код.

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

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

22. Коментари.

Може би вече сте забелязали, че обичам да поддържам правилата за кодиране прости, така че да са лесни за следване от всеки в екипа. Същото е и с коментарите.

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

Каквото и да си мислите, когато внедрявате функцията (подходяща за работа!), е добре да напишете в коментарите. Особено как работи всичко, как се използва клас, каква е целта на това enum и т.н. Целта е много важна, тъй като е трудно да се обясни само чрез правилно именуване, освен ако някой вече не знае конвенциите предварително.

Разбирам, че „InjectorToken“ има пълен смисъл за вас и може да го сметнете за „разбиращ се сам“. Честно казано, това е страхотно име. Но това, което искам да знам, когато разглеждам този клас, е — за какво е този токен, какво прави, как мога да го използвам и какво представлява този инжектор. Би било идеално да видите това в коментарите, така че никой да не преглежда цялото приложение, нали?

23. Документация.

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

Можете да използвате Doxygen за автоматично генериране на документация.

Заключение

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

Първоначално публикувано в alemil.com.