Я решил вернуться к старой классике для своей первой статьи.

О книге

Чистый код — это идея, изложенная в книге под названием Чистый код, написанной Робертом Мартином в 2008 году.

Что это такое?

Чистое кодирование — это набор принципов и руководств, которые помогают создавать интуитивно понятный и легко модифицируемый код.

Чистый код имеет следующие характеристики:

  • Классы и методы предсказуемы и сведены к выполнению одной конкретной задачи.
  • Независимо от создавшего его разработчика.
  • Хорошо протестировано

Преимущества:

  • Упрощает обслуживание системы.
  • Предотвращает деградацию кода.

Цена беспорядка

Деградация кода — серьезная проблема, которая со временем снизит производительность и, если ее не остановить в течение длительного периода времени, в конечном итоге помешает разработчикам добавлять новые функции в систему.

Потеря контроля над своим кодом

Со временем последствия беспорядочного кода станут очевидными, и если с этим не справиться, это вызовет цепочку событий.

  1. Грязный код со временем снизит производительность разработчиков
  2. Менеджмент отреагирует усилением давления на членов команды и привлечением к проекту сотрудников.
  3. Новый персонал не знаком с конструкцией системы.
  4. Все в команде находятся под ужасным давлением, чтобы повысить производительность, поэтому все они делают все больше и больше беспорядка.
  5. Команда теряет возможность добавлять в продукт новые функции.
  6. Команде разработчиков потребуется переписать систему

Как предотвратить эту проблему

Чтобы предотвратить эту проблему, каждый член команды должен помнить о наборе принципов при ежедневном добавлении или изменении кода.

По сути, каждый раз, когда мы редактируем код, мы должны оставить его немного лучше, чем когда мы его нашли.

Методические рекомендации

Значимые имена

Понятные, Имена должны раскрывать намерение.

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

Плохой

const d = 17; // days 

Хороший

const daysRemaining = 17;

Избегайте общих слов

Шумные слова — это слова, не несущие никакой дополнительной информации о переменной. Они избыточны и должны быть удалены.

  • Информация
  • Данные
  • Переменная
  • Объект
  • Менеджер

Плохой

const userData = { name: 'John' };
const user = userData.name;

Хороший

const user = { name: 'John' };
const userName = user.name;

Используйте доступные для поиска имена

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

Плохо

if (t > 150) {
	// do something
}

Хороший

const MAX_TEMPERATURE = 150;
if (currentTemperature > MAX_TEMPERATURE) {
	// do something
}

Используйте произносимые имена

Имена должны быть настоящими словами. Если вы не можете произнести имя, вы не можете его обсуждать.

Плохой

const yyyymmdstr = moment().format("YYYY/MM/DD");

Хороший

const currentDate = moment().format("YYYY/MM/DD");

Не должен нуждаться в комментариях

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

// this is current date
const yyyymmdstr = moment().format("YYYY/MM/DD");

Имена переменных должны быть существительными Имена методов должны быть глаголами

Переменные и классы должны быть существительными, а функции и методы — глаголами.

const shutdown = true;
const serve = false;

function main () {

}

function number () {

}

function thing() {

}

Хороший

const isShutdown = true;

function shutdown () {

}

function initialize() {

}

Функции

Должен быть маленьким

Функции должны быть маленькими, действительно маленькими. Они редко должны быть длиной 20 строк. Чем длиннее функция, тем больше вероятность того, что она будет делать несколько вещей и иметь побочные эффекты.

Делай одно дело и делай это хорошо

Чтобы убедиться, что наши функции делают «одно дело», нам нужно убедиться, что все операторы в нашей функции находятся на одном уровне абстракции.

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

Плохой

function saveForm(form) {
  if (!form.name && !form.email) {
    // throw error
  }
	
  const response = await fetch('/settings', {
    method: 'POST', 
    mode: 'cors', 
    cache: 'no-cache',
    credentials: 'same-origin', 
    headers: {
      'Content-Type': 'application/json'
    },
    redirect: 'follow', 
    referrerPolicy: 'no-referrer', 
    body: JSON.stringify(form) 
  });
  return response;
}

Хороший

function postRequest({ url, data, options}) {
 const response = await fetch(url, {
    method: 'POST', 
    mode: 'cors', 
    cache: 'no-cache',
    credentials: 'same-origin', 
    headers: {
      'Content-Type': 'application/json'
    },
    redirect: 'follow', 
    referrerPolicy: 'no-referrer', 
    body: JSON.stringify(data),
    ...options 
  });
	return response;
}

function isValidSettingsForUpdate(settings) {
	if (!settings.name || !settings.email) {
		return false;
	}
	return true;
}

function updateSettings(settings) {
	if (!isValidSettingsForUpdate(settings) {
		// throw error
	}
	
	const response = await postRequest({
		url: '/settings',
		data: settings
	});
	
	return response;
}

Иметь не более 3-х параметров.

Функции должны иметь два или меньше параметров, чем меньше, тем лучше. По возможности избегайте трех или более параметров.

Плохой

function createUser(name, lastName, nickName, role, grade, birthDate, hiredDate, ...) {
	// do something
}

createUser('John', 'Doe', 'Johnny', 'manager', 'senior', '10/10/1987', '10/11/2022', ...)

Хороший

function createUser(user) {
  // do something
}


createUser({
  name:'John', 
  lastName: 'Doe',
  role: 'manager', 
  grade: 'senior', 
  nickName: 'Johnnie',
  birthDate: '10/10/1987', 
  hiredDate: '10/11/2022', 
});

Избегайте параметров флага.

Параметр флага — это логическое значение, которое передается функции. В зависимости от значения этого параметра выполняются два разных действия.

Параметр флага — это четкий сигнал о том, что функция выполняет несколько действий.

Плохо

function createOrder(order, shouldValidate) {
  if (shouldValidate) {
    if (isOrderValid(order) {
      // logic for create order
    }
  } else {
    // logic for create order
  }
}

function main() {
  createOrder(order, true);
}

Хороший

function createOrder(order) {
  // logic for create order
}

function isOrderValid(order) {
  // logic for validation 
}

function main() {
  if (isOrderValid(order)) {
    createOrder(order);	
  }
}

Не имеют побочных эффектов.

Побочные эффекты — это непреднамеренные последствия вашего кода. Это неожиданное и непредсказуемое поведение, вызванное выполнением функции.

Плохой

function createUser(user) {
  // logic for create user
  db.create(user);
  initializeSession(user);
}

function isUserValid(user) {
  // logic for validation 
}

function main() {
  if (isUserValid(user)) {
    createUser(user);	
  }
}

Хороший

function createUser(user) {
  // logic for create user
}

function isUserValid(user) {
  // logic for validation 
}

function main() {
  if (isUserValid(user)) {
    createUser(user);	
    initializeSession(user)
  }
}

Либо делай что-то, либо отвечай на что-то, но не то и другое одновременно.

Либо ваша функция должна изменить состояние объекта, либо она должна вернуть некоторую информацию об этом объекте. Выполнение обоих часто приводит к путанице.

Плохой

function setUserName(user, name) {
  if (isValidUserName(name)) {
    user.name = name;
    return true;
  }
  return false;
}

function isValidUserName(name) {
  // logic for validation 
}

function main() {
  if (setUserName(user, 'name')) {
    // logic for userName is set
  }
}

Хороший

function setUserName(user, name) {
  user.name = name;
}

function isValidUserName(name) {
  // logic for validation 
}

function main() {
  if (isValidUserName(name)) {
    setUserName(user, 'name');
    // logic for userName is set
  }
}

Классы

Элементы класса должны быть отсортированы в следующем порядке.

  1. Общедоступные константы
  2. Частные константы
  3. Публичные переменные (редко)
  4. Частные переменные
  5. Публичные методы
  6. Частные методы (под публичными методами, которые его вызывают)

Маленький

Класс должен быть небольшим и сосредоточенным на своей основной цели, он должен расти только по одной конкретной причине.

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

Класс должен иметь одну и только одну причину для изменения.

Подсказки, указывающие на то, что класс делает слишком много

  • Задействовано более одного уровня архитектуры
  • Количество публичных методов
  • Объем импорта
  • Класс трудно проверить
  • Каждый раз, когда мы добавляем функцию, класс должен быть изменен
  • Количество строк кода

Плохой

public  class  SuperDashboard  extends  JFrame  {
	public Component getLastFocusedComponent()  {...}
	public void  setLastFocusedComponent(Component lastFocusedComponent)  {...}
	public int  getMajorVersionNumber  ()  {...}
	public int  getMinorVersionNumber  ()  {...}
	public int  getBuildNumber  ()  {...}
}

Хороший

public class Version {
	public int getMajorVersionNumber () {...}
	public int getMinorVersionNumber () {...}
	public int getBuildNumber () {...}
}

public  class  SuperDashboard  extends  JFrame  {
	public Component getLastFocusedComponent()  {...}
	public void  setLastFocusedComponent(Component lastFocusedComponent)  {...}
	private Version version;
}

Класс SuperDashboard делает большую часть основной проблемы в этом примере, поскольку этот класс имеет дело с двумя концепциями компонентов панели мониторинга и VersionNumber и BuildNumbers, которые на самом деле ему не принадлежат.

Сплоченный

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

Помните о законе Деметры

Закон Деметры требует, чтобы класс не знал деталей объектов, которыми он манипулирует.

Закон Деметры определяет только два требования, но они должны выполняться каждым методом в коде. Один метод может работать только с объектами, которые:

  • передается в качестве аргумента в метод
  • значения полей, определенных в этом классе

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

Плохой

public class Car {
    private Engine engine;

    public void start() {
        engine.turnOnElectronics();
	engine.turnOnFuelSystem();
	engine.turnOver();
    }
}


public class Bike {
    private EngineStarter engineStarter;
    private Engine engine;

    public void start() {
        engine.turnOnElectronics();
	engine.turnOnFuelSystem();
	engine.turnOver();
    }
}

Хороший

public class EngineStarter {
  ElectriCMotor electricMotor;

  public void turnOnEngine(Engine engine) {
    engine.turnOnElectronics();
    engine.turnOnFuelSystem();
    electricMotor.turnOver(engine);
  }
}

public class Car {
    private EngineStarter engineStarter;
    private Engine engine;

    public void start() {
        engineStarter.turnOnEngine(this.engine);
    }
}


public class Bike {
    private EngineStarter engineStarter;
    private Engine engine;

    public void start() {
        engineStarter.turnOnEngine(this.engine);
    }
}

Комментарии

Вместо добавления комментария, объясняющего, что делает код, мы предпочитаем писать код таким образом, чтобы сам код объяснял, что он делает.

Однако бывают случаи, когда добавление комментариев действительно необходимо. Затем мы должны написать хорошие комментарии, хорошие комментарии объясняют, чего код не делает, как он это делает.

Хорошие комментарии:

Юридические комментарии

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

/* 
  Released under the whatever license
  Copyright company.com
  we will sue you if you use this without telling us
  created by thepreviousguy
*/
	
function ChartLibrary() {
		
}

Информативные комментарии

Полезно поделиться намерением, которое у нас есть во время написания кода.

function calculateUserAge(user) {
  // the expected format for birthDate is dd/mm/yyy
  const birDate = moment(user.birthDate);
	return moment().difference(birthDate);
}

function main() {
	calculateUserAge(user)
}

Усиления

Объясняет часть неочевидной информации или указывает на важность чего-либо.

function parseVINS(text) {
  const vins = [];
  const regex = /[A-Z1-9]{1,9}/g;
  const matches = regex.exec(text);
  if (!matches) {
    return [];
  }
  for(let match of matches) {
	// the trim is real important. It removes the starting  
	// spaces that could cause the item to be recognized  
	// as another vin.
	const vin = match.group[0].trim();
	vins.push(vin)
  }
  return vins;
}

function main() {
  parseVINS(textContent)
}

Предупреждающие комментарии

Иногда полезно предупредить других программистов об определенных последствиях.

/* 
  Dont run unless you have some time to clean up afterwards
*/
	
function testWithBigFile() {
  const lines = file.open('/really_big_file');
  for(let line of lines) {
    ordersTable.insert(buildOrderFromFileRow(line));
  }
  filterOrders();
  const orders = ordersTable.findAll();
  assertTrue(orders.length === 5000);
}

Комментарии TODO

Иногда разумно оставлять заметки «Что нужно сделать» в виде комментариев //TODO. В следующем случае комментарий TODO объясняет, почему функция имеет вырожденную реализацию и каким должно быть будущее этой функции.

/* 
  TODO: remove this function, it is no longer needed 
  We expect this mehod to be removed after integration with third party provider
*/
	
function loadGeographicalAreas() {
	// code to load geo data	
}

Плохие комментарии:

Обязательные комментарии

Просто глупо иметь правило, которое говорит, что каждая функция должна иметь javadoc или каждая переменная должна иметь комментарий. Подобные комментарии только загромождают код, распространяют ложь и вносят путаницу и дезорганизацию.

бормотание

Комментарии, которые имеют смысл только для того, кто их написал.

function loadSettings() {
  try {
    const settings = await file.open(settingsDirectory + '/' + settingsFileName);
    initializeSettings(settings)
  } catch () {
    // default 
  }
}

Избыточный

Не добавляйте комментарии, когда читаете код, объясняющий, что он делает.

// wait for closed to be true
function waitForClose() {
  if (!this.closed) {
    await wait(timeout)
  }
}

Вводящий в заблуждение

Комментарии, содержащие ложную информацию

// wait for closed to be true
// returns true when closed
function waitForClose() {
  if (!this.closed) {
    await wait(timeout)
  }
}

Журналы

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

/*
  changelog
  v1 added main function
  v1.1 removed old main
*/

function main() {
  if (isValidUserName(name)) {
    setUserName(user, 'name');
    // logic for userName is set
  }
}

Старый код

Оставлять старый код в комментариях — ужасная практика, которая вызывает много путаницы. этот комментарий надо удалить

/* 
	function oldmain() {
		..bunch of old code
	}
*/

function main() {
  if (isValidUserName(name)) {
    setUserName(user, 'name');
    // logic for userName is set
  }
}

Тесты

Одна концепция на тест

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

Следуйте процессу TDD

  • Первый закон. Вы не можете писать производственный код, пока не напишете неудавшийся модульный тест.
  • Второй закон Вы не можете написать больше модульного теста, чем достаточно для провала, а не скомпилировать — значит провалиться.
  • Третий закон. Вы не можете написать больше производственного кода, чем достаточно для прохождения текущего неуспешного теста.

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

Соблюдайте правила F.I.R.S.T.

Быстро

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

Независимый

Тесты не должны зависеть друг от друга. Один тест не должен создавать условия для следующего теста. Вы должны иметь возможность запускать каждый тест независимо и запускать тесты в любом порядке.

Повторяемый

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

Самопроверка

Тесты должны иметь логический вывод. Либо они проходят, либо терпят неудачу. Вам не нужно читать файл журнала, чтобы узнать, пройдены ли тесты. Результат теста не должен зависеть от результатов других тестов.

Своевременно

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

Другие упомянутые принципы

ТВЕРДЫЙ

SOLID — это аббревиатура первых пяти принципов объектно-ориентированного проектирования (ООП) Роберта К. Мартина.

Правило бойскаута

Недостаточно хорошо написать код. Код должен содержаться в чистоте с течением времени. Мы все видели, как код гниет и деградирует с течением времени. Поэтому мы должны играть активную роль в предотвращении этой деградации.

У бойскаутов Америки есть простое правило, которое мы можем применить к нашей профессии.

Leave the campground cleaner than you found it.

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

Наименьший сюрприз

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

Заключение

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

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

Чтение этой книги изменит ваше мышление при программировании.