🧐 Очевидно и явно

Очевидное неочевидно, а явное не явно

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

То что очевидно вам, может быть неочевидно другим, даже если вам очевидно, что ему тоже очевидно.

Простое правило: Никому ничего не очевидно. Если вы примете это правило, вам станет гораздо проще жить, хоть это может и не показаться таковым на первый взгляд.

Магия вне Хогвартса

Давайте оставим свои магические способности для компьютерных механик и будем избегать их в коде.

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

Например вы решили какую-то сложную проблему, со стороны удобно ее использовать, но механизм работы настолько запутан, что если туда полезть - все равно что попасть в средиземье.

Коперфильд

Для React/Vue/Angular разработчиков примером может быть внезапно появившийся props, которого он ниоткуда сам не передавал и не ожидал.

В классе появилось свойство из ниоткуда, а в функции родился неожиданный контекст.

Примеры:

function MySuperCoperfield({ children, ...props }: Props) {
    return React.cloneChildren(children, { ...props, type: 'wzhooh' });
}
handleMouse(event) {
    this.smt // work with this
}

// call with another context(this)
handleMouse.apply({}, ...args)

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

Павукан

Неочевидные связи могут так же испортить вам жизнь. Есть разработчики, которые как пауки, людят плести связный код и ловить доверчивых программистов в свои коварные сети.

Такие связи часто образуют через глобальные переменные, глобальные сторы. Это все можно использовать, но осторожно, следя за связями.

// === file Foo
import * as bar from './bar';

export function fn() {
    bar.fn();
}

// === file Bar
import * as baz from './baz';

export function fn() {
    baz.fn();
}

// === file Baz
import * as foo from './foo';

export function fn() {
    foo.fn();
}

Соколиный глаз

Иногда встречаются конструкции циклами или условиями без скобок - это довольно багоопасная штука.

if (disabled) return;

while (condition) doIt();

for (let i = 0; i < arr.length; i++) doIt(arr[i]);

Кажется довольно безобидным, особенно вызод из функции через return, сколько таких можно встретить в open source, библиотеках, документации. Если можно с return, значит можно и с чем-то другим. Так же в if может быть куда более большая проверка. Две дополнительные строки не сделают погоды, зато сэкономят много времени другому разработчику.

Опасна эта конструкция тем, что можно упустить область работы блока.

Порефакторил и получил что-то такое

if (isValid(foo) && bar && exists(baz) && isSuper && type === 'submit') return;

Конечно тут лучше хорошенько отрефакторить. Но если к вам придет не очень опытный разработчик и будет добавлять функционал, это может превратиться в такое и отсутсвие {} может сыгарть с вами или ним злую шутку.

Язык rust специально не стал добавлять такой способ работы блочных вычислений.

Что говоря про отрицание - часто лучше напрямую сравнивать, нежели неявно преобразовывать через !.

// ❌ BAD:
if (!isSome) {
}
if (!INTERNAL) {
}

// ✅ Good:
if (isSome === false) {
}
if (INTERNAL === false) {
}

В таких случаях вы сразу видите, что это boolean и он проверяется на false. Инвертирование в голове в целом не очень быстро проходит и люди мысленно "спотыкаются" при прочтении на отрицании в проверках.

Пожалуй единственное, где я использую отрицание - это на опциональных значених, которые может быть object | null | undefined. Делать две проверки вместо одной еще хуже сказывается на чтении.

Можете считать меня слепым, но мне иногда сложно заметить ! или отсутсвие {} у if. Из-за несоблюдения данных правил, я создавал баги при рефакторинге чужого кода.