Сучасна диджитал-освіта для дітей — безоплатне заняття в GoITeens ×
Mazda CX 5
×

7 Заповідей Читабельного Коду

Усі статті, обговорення, новини про Front-end — в одному місці. Підписуйтеся на телеграм-канал!

Про що ця стаття..

..і чому це важливо навіть для тих, хто ніколи не торкався, і можливо не доторкнеться до JS/TS? Хоча деякі мови вигідно/невигідно відрізняються, більшість все ж таки залишається дуже схожою, therefore, багато чого з цього тексту може бути вам корисним. Також, не слід забувати що мова — це лише інструмент, і головний фактор, який впливає на якість/читабельність коду — це ви та ваш підхід до його написання. Тож зараз ми розберемо на прикладах, деякі базові «помилки», уникання яких допоможе не тільки вашій команді, але й вам самому через деякий час.

true чи не true, ось у чому питання

Розглянемо код. Як думаєте, що з ним не так?

function isGoodBoy(boy) {
    if (boy.isGood) {
        return true;
    } else {
        return false;
    }
}

Багато хто скаже — з ним все добре, і насправді так і є. Проте, якщо уважно вивчити його, можна зрозуміти, що ми робимо забагато «зайвих рухів», які не просто не роблять картину ліпше, а й псують її. Навіщо використовувати if-else, якщо значення умови — напряму зв’язане зі значенням, що повертається. Простий аналіз вашого коду — завжди викриє такі недоліки.

function isGoodBoy(boy) {
    return boy.isGood;
}

let me use const


function calculation(a, b, c) {
    let division = a / b;
    return division * c;
}

Дивно, проте велика частина людей ігнорує той факт що в ES6, разом із let, 7 років назад, прийшов const. Звичайно, іноді без let нікуди, — хоча це спірно :D, тому я завжди рекомендую правило:

Якщо змінна може бути const, вона МАЄ бути const.

Чи варто ризикувати надійністю та читабельність коду, — а іноді доволі важко вгледіти за змінною, яка постійно перевизначається,

function calculation(a, b, c) {
    let division = a / b;
    // some code
    division = 'oops';
    // some code
    return division * c;
}

замість того щоб писати на 2 літери більше. Відповідь за вами.


function calculation(a, b, c) {
    const division = a / b;
    return division * c;
}

Не став мені умови, тим паче такі!

Чи бачили ви колись умовні оператори з такими умовами?

if (user.tags[0].find(({name, enc}) => name === 'user' && enc === 'sha256')) {
    // do something
}

— Конкретно такі, можливо що ні, проте великі та незрозумілі умови — це дуже поширена проблема. Ще дуже давно я зрозумів, що якщо ставити такі вирази в if, можна залишитися не лише без readable коду, а й без друзів. Це стосується не лише if, але усіх місць де використовується вираз, який перетворюється на значення boolean. Гарна контрміра — виносити всі ці вирази у змінні, що мають логічні та зрозумілі назви. Consider:


const userTagIsCompatible = user.tags[0].find(({name, enc}) => name === 'user' && enc === 'sha256');
if (userTagIsCompatible) {
    // do something
}

Who else if not me?


function handleEvent(event) {
    if (event.metadata) {
        if (event.type === 'mouse') {
            // do something
            return 'if string';
        } else {
            // do something else
            return 'else string';
        }
    }
    return 'default case string';
}

Цю тему дуже часто висвітлюють всеможливі блогери та ютубери, просто тому, що для молодих розробників, це може бути щось не очевидне. Менеджмент if-else’ів — це доволі велика тема, проте, якщо говорити по суті — намагайтеся використовувати guard кейси, та реверсувати if з else, за допомогою перевертання умови.


function handleEvent(event) {
    // guard case
    if (!event.metadata) return 'default case string';
    if (event.type === 'mouse') {
        // do something
        return 'if string';
    }
    // do something else
    return 'else string';
}

Часто, else може навіть не знадобитись, що своєю чергою, зменшить nesting, і збільшить readability.

Decoupling та єдина відповідальність

Зіграймо в гру! На рахунок 3 всі, у кого були функції більше ніж 100 рядків, — стрибне! Насправді це погана ідея, тому, що Землю, скоріш за все, знесе з орбіти сонця. Всі хто мав/має таку функцію думав — «Можливо, все ж таки, винести трішки коду», проте ця ідея, на жаль, так і залишається ідеєю. Так от. Це ОБОВ’ЯЗКОВО треба робити, це не тільки допомагає проєкту триматися на курсі best practice, а ще тому, що нову «модернізовану» функцію можна буде читати як вірш Шевченка.


function read(userInput) {
    if (userInput.rights.read) {
        const sanitizedInput = sanitizeInput(userInput.content);
        return processInput(sanitizedInput)
    }
    return handleInputWithoutRights(userInput);
}

Magic strings, або «Як воно там написано, я забув»


const user = createUser({name: 'Volodymyr', role: 'user'});
if (user.getRole() === 'user') {
    // do something
}

Хоча я більш ніж впевнений, що всі знають про magic strings, все ж таки поясню. Магічні рядки — це strings (numbers, booleans тощо), які зустрічаються більш ніж в одному місці та не винесені в одну, єдину змінну (константу/поле enum). Це не тільки зробить процес розробки набагато зручнішими, — та як не прийдеться, за потреби, шукати всі місця де вони зустрічаються, щоб змінити, але й використання змінної дає розуміння всім навколо, що ж це за значення в ній зберігається.


const USER_ROLE = 'user';
const user = createUser({name: 'Volodymyr', role: USER_ROLE});
if (user.getRole() === USER_ROLE) {
    // do something
}

«The sky is the limit»

Хоча ті пункти, які я перерахував — це всього лишень 1% від всіх які, для розробника будь-якого рівня, було б доцільно розглянути, проте, найголовніший пункт — це цікавитись, слухати людей яких ви вважаєте авторитетами у цій темі і розвиватися. Завжди.

👍ПодобаєтьсяСподобалось8
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Рекомендую прочитати книгу Чистий Код (Роберт Мартін).

Дисклеймер. Я джавіст, з JavaScript/TypeScript не знайомий, тож може не розумію якусь специфіку, але здається приклади досить загальні.

1. Не зрозуміло навіщо переобертати об’єктний метод/проперті boy.isGood функцією в процедурному стилі.

2. Стосовно else притримуюсь правила (і заохочую інших) — гілки then і else мають «збалансовані». Таке буває рідко, тому else краще уникати, тобто

if (condition) {
    doSomething();
} else {
    doSomethingElse();
}
норм.

А

if (condition) {
   doSomething();
} else {
   log.info("Won't do");
}

— ні, тому що логування не «еквівалентне» дії. Краще

if (!condition) {
   log.info("Won't do");
   return;
} 
doSomething();  

Відповідно

function handleEvent(event) {
    // guard case
    if (!event.metadata) return 'default case string';
    if (event.type === 'mouse') {
        // do something
        return 'if string';
    }
    // do something else
    return 'else string';
}
я б написав
function handleEvent(event) {
    if (event.metadata) {
        if (event.type === 'mouse') {
            // do something
            return 'if string';
        } 
        // do something else
        return 'else string';
    }
    return 'default case string';
}
тобто спочатку йдуть exceptional cases, а далі — main flow

Здається, в першому прикладі хтось пропустив {

Невже спільнота фронт-енд девелоперів настільки деградувала, що реально потребує порад на кшталт цього?

Я і раніше жодних ілюзій не плекав, але щоб настільки... Жах та пипець.

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

До прикладу із 1 пункту можна прискіпнутись

Початковий код вертає строго bool — true або false — не залежно від того, якого типу є boy.isGood.

Новий код вже вертає саме значення boy.isGood разом із його типом.

Це може стати проблемою, якщо ми очікуємо, що функція поверне строго bool, тому для таких випадків варто все таки конвертувати значення:

function isGoodBoy(boy) {
    return !!boy.isGood;
}

Magic strings, и аналогично им magic numbers не просто не анти-паттерн в TS, но самый лаконичный способ написания кода. Важное условие — переменные должны иметь не просто тип string/number но union type

type UserRole = 'user' | 'admin'

type User<Role extends UserRole> = { name: string, role: Role, getRole: () => Role }

const user = createUser({name: 'Volodymyr', role: 'user'});

if (user.getRole() === 'user') {

    // do something

}

Вы получите все преимущества enum: компилятор не даст вам использовать неправильное значение и редактор будет давать autocomplete. Но код, в сравнении с использование enum, будет более компактным, так как отпадает необходимость везде импортить еnum и в каждом месте использования указывать namespace, типа USER_ROLES.USER

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

Цікаво, яка може бути причина 2023 року писати на JavaScript, а не на TypeScript.

Така сама як зараз фігачать наприклад на Delphi або існує неймовірна кількість вакансій PHP /JQuery

Підтримую Вільного Художника, і від себе додам, що слідкуючи за рампою розвитку JS, через деякий час, він, потенційно, може дати непоганий бій ТSу) Як мінімум, через непотрібність компілятора.

Ще з Пітону:

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one— and preferably only one —obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!

peps.python.org/pep-0020

Є хороша маловідома книжка A Philosophy of Software Design.
Там більше за розбивку коду на модулі, щоб результат найлегше було читати. Яким має бути інтерфейс модуля чи класу.

Підписатись на коментарі