Clean code

Часть 1, Часть 2

Не забывайте про принцип DRY (Не повторяйтесь)

Принцип DRY гласит:

“ Каждая часть знания должна иметь единственное, непротиворечивое и авторитетное представление в рамках системы.”

На простейшем уровне это означает, что нужно стремиться к сокращению количества дублированного кода. (Именно к “сокращению, а не “устранению”. Присутствие дублированного кода в некоторых экземплярах — это не конец света!)

Иногда дублированный код трудно поддерживать и изменять. Рассмотрим пример:

Представьте, что вы разрабатываете для клиента приложение по управлению персоналом. Администраторы могут добавлять пользователей с ролями в базу данных приложения через API. Есть 3 роли: сотрудник, менеджер и администратор. Рассмотрим несколько возможных функций:

function addEmployee(){ 
    // create the user object and give the role
    const user = {
        firstName: 'Rory',
        lastName: 'Millar',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}
function addManager(){  
    // create the user object and give the role
    const user = {
        firstName: 'James',
        lastName: 'Marley',
        role: 'Admin'
    }
    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}
function addAdmin(){    
    // create the user object and give the role
    const user = {
        firstName: 'Gary',
        lastName: 'Judge',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    axios.post('/user', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
}

Круто! Код работает. Спустя некоторое время клиент приходит и говорит:

Привет! Мы бы хотели, чтобы сообщение об ошибке содержало слова “произошла ошибка”. Кроме того, мы хотим, чтобы конечная точка API была изменена с /user на /users. Спасибо!

Прежде чем приступить к написанию кода, оглянемся назад. В первой части цикла было сказано о том, что чистый код должен быть целенаправленным, т. е. хорошо выполнять определенную функцию. Именно в этом заключается проблема кода в данном примере. Код, совершающий вызов API и обрабатывающий ошибки, повторяется. Это означает, что нужно изменить код в трех местах для соответствия новым требованиям!

Почему бы не выполнить рефакторинг кода, чтобы сделать его более целенаправленным? Рассмотрим следующий фрагмент:

function addEmployee(){ 
    // create the user object and give the role
    const user = {
        firstName: 'Rory',
        lastName: 'Millar',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}
function addManager(){  
    // create the user object and give the role
    const user = {
        firstName: 'James',
        lastName: 'Marley',
        role: 'Admin'
    }
    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}
function addAdmin(){    
    // create the user object and give the role
    const user = {
        firstName: 'Gary',
        lastName: 'Judge',
        role: 'Admin'
    }
    
    // add the new user to the database - and log out the response or error
    saveUserToDatabase(user);
}
function saveUserToDatabase(user){
    axios.post('/users', user)
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log("there was an error " + error);
  });
}

Логика, создающая вызов API, перемещена в отдельный метод saveUserToDatabase(user) (хорошее ли название? Решать вам!), который будет вызван другими методами для сохранения пользователя. Теперь для повторного изменения логики API нужно обновить лишь один метод. Ура!

Пример рефакторинга на основе изученных материалов

Представьте, что нужно создать приложение “калькулятор”. Для этого понадобятся функции для сложения, вычитания, умножения и деления. Результат будет отображаться в консоли.

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

function addNumbers(number1, number2)
{
    const result = number1 + number2;
        const output = 'The result is ' + result;
        console.log(output);
}
// this function substracts 2 numbers
function substractNumbers(number1, number2){
    
    //store the result in a variable called result
    const result = number1 - number2;
    const output = 'The result is ' + result;
    console.log(output);
}
function doStuffWithNumbers(number1, number2){
    const result = number1 * number2;
    const output = 'The result is ' + result;
    console.log(output);
}
function divideNumbers(x, y){
    const result = number1 / number2;
    const output = 'The result is ' + result;
    console.log(output);
}

В чем заключаются ошибки?

  • Непоследовательность отступов. Формат отступов не важен, если они последовательны.
  • Излишние комментарии во второй функции. Определить, что происходит внутри функции, можно с помощью ее названия и содержащегося кода.
  • Плохие названия третьей и четвертой функций. По названию doStuffWithNumbers() невозможно определить, что выполняет эта функция. (x, y) также не являются описательными переменными — что такое функции x & y? Числа? Бананы?
  • Методы выполняют больше одного действия. Помимо выполнения подсчетов они отображают результат. Логику отображения можно поместить в отдельный метод согласно принципу DRY.

Теперь используем информацию из руководства для рефакторинга кода и получаем следующее:

function addNumbers(number1, number2){
	const result = number1 + number2;
	displayOutput(result)
}

function substractNumbers(number1, number2){
	const result = number1 - number2;
	displayOutput(result)
}

function multiplyNumbers(number1, number2){
	const result = number1 * number2;
	displayOutput(result)
}

function divideNumbers(number1, number2){
	const result = number1 * number2;
	displayOutput(result)
}

function displayOutput(result){
	const output = 'The result is ' + result;
	console.log(output);
}
  • Восстановлена последовательность отступов.
  • Названия функций и переменных подкорректированы.
  • Удалены лишние комментарии.
  • Логика displayOutput() перемещена в отдельный метод — при необходимости изменения результата его можно изменить в одном месте.

Поздравляем! Теперь вы разбираетесь в принципах чистого кода!

Не “перечищайте” код

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

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

В этом руководстве в двух частях (первая часть) по написанию чистого кода для новичков мы узнали, как использовать следующее:

  • Последовательное форматирование и отступы.
  • Понятные названия переменных и методов.
  • Комментарии.
  • Принцип DRY.

Спасибо за внимание!


Перевод статьи Chris Blakely: The junior developer’s guide to writing super clean and readable code