Прежде чем перейти к теме, выясним, что такое чистый код.

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

Цель чистого кода  —  сделать исходный код понятным как для нынешних, так и для будущих разработчиков, что способствует сотрудничеству и облегчает постоянное сопровождение и оптимизацию программного обеспечения. Принципы чистого кода наиболее подробно изложены в книге Роберта К. Мартина “Чистый код: руководство по гибкой разработке программного обеспечения”.

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

Вот 8 советов, которые сделают JavaScript-код более читабельным и чистым.

1. Всегда используйте осмысленные имена для переменных

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

//Плохо
const a = "Dafa Pramudya"
const b = "Male"

//Хорошо
const fullName = "Dafa Pramudya"
const gender = "Male"

Обратите внимание: при именовании переменной необходимо использовать регистр camelCase (горбатый), например слово1/слово1Слово2.

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

const isActive = true;
const canEdit = false;
const hasPermission = true;

Если переменная должна хранить значение результата функции, используйте существительное или добавьте слово “result” перед именем:

const cars = getCars()
const carById = getCar(id)
const updatedCar = updateCar(id)
const resultDeletedCar = deleteCar(id)

Это относится и к именованию функций.

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

Создание функций с большим количеством параметров  —  не самая лучшая практика. Она может приводить к путанице, особенно когда код читают другие люди. Так, будущие разработчики испытают немало трудностей при попытке понять, какие обязательные или необязательные параметры нужно заполнять, особенно при условии добавления дополнительных параметров. Лучшая практика заключается в установлении максимального количества параметров в каждой функции (по крайней мере, не более трех-четырех).

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

//Плохо
function registerUser(fullName, email, phoneNumber, gender, address, hobbies) {
//Логика регистрации пользователя
}


//Хорошо
function registerUser(data) {
const {
fullName, email, phoneNumber,
gender, address, hobbies
} = data
//Логика регистрации пользователя
}

Затем в функции registerUser можно применить оператор деструктуризации для получения данных.

3. Избегайте создания условий, содержащих сложные проверки

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

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

//Плохо
if(role === 'admin' && projectMember.length && projectStatus === 'confirmed') {
//Логическая функция
}


//Хорошо
const isAdminCanApprove = checkAdminCanApprove(data);
if(isAdminCanApprove) {
//Логика
}
//Или (альтернатива)
if(isAdminCanApprove(data)) {
//Логика
}

function checkAdminCanApprove(data) {
try {
const {
role,
projectMember,
projectStatus
} = data;
if(role === 'admin' && projectMember.length && projectStatus === 'confirmed') {
return true;
}
return false;
}catch(err) {
//Обработка ошибки
}
}

Кажется, что код “Хорошо” длиннее, но на самом деле он облегчит чтение условия, потому что, увидев условие, мы сразу поймем его цель  —  проверить, может ли администратор одобрить проект. Этот метод строго следует принципу DRY (Dont Repeat Yourself  —  не повторяйся): при столкновении с тем же условием проверки нужно вызвать ранее созданную функцию.

4. Следите за тем, чтобы функция выполняла только одну задачу

Функцию, у которой много задач, будет очень трудно поддерживать. Поэтому создавайте функции, каждая из которых выполняет только одну задачу, следуя принципу единственной ответственности (Single Responsibility Principle, SRP)  —  одному из SOLID-принципов объектно-ориентированного проектирования.

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

// Несоблюдение принципа единственной ответственности
function processUserDataAndGenerateReport(user) {
// Обработка данных пользователя
// ...
// Генерация отчета
// ...
}

// Соблюдение принципа единственной ответственности
function processUserData(user) {
// Обработка данных пользователя
// ...
}

function generateReport(user) {
// Генерация отчета
// ...
}

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

5. Обязательно ведите журнал (лог)

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

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

function processMultiplyDataBy2(data) {
console.log('Processing data started:', new Date());

try {
const processedData = data.map(item => item * 2);

console.log('Data processing successful:', new Date());

return processedData;
} catch (error) {
console.error('Error during data processing:', error, new Date());

throw error;
}
}



const dataNumbers = [1, 2, 3, 4, 5];

try {
const result = processMultiplyDataBy2(dataNumbers);
console.log('Processed data:', result);
} catch (error) {
console.error('Error handling:', error);
}

Применяйте разные уровни логирования (например, info, warning, error), потому что они помогают классифицировать серьезность оповещений. Это позволяет разработчикам распределять приоритеты и фокусироваться на нужной информации.

6. Всегда используйте try-catch при создании функций

Когда нужно отследить или обработать ошибку, можно использовать блок try-catch. Применяйте try-catch, когда хотите обработать потенциальные ошибки, которые могут возникнуть во время выполнения функции. Это особенно важно в случае критических операций или при работе с внешними ресурсами.

function divide(a, b) {
try {
if (b === 0) {
throw new Error('Division by zero is not allowed.');
}
return a / b;
} catch (error) {
console.error('Error:', error.message);
// Обработайте ошибку или занесите ее в журнал (на выбор).
// Можете также повторно выбросить ошибку при необходимости.
}

Блок try-catch должен быть обязательно реализован в асинхронной функции. В противном случае необработанные ошибки могут привести к аварийному завершению работы приложения или неожиданному поведению.

async function fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error.message);
// При необходимости обработайте ошибку или запишите ее в журнал.
}
}

7. Создавайте короткие комментарии к документации, связанные с объявленной функцией

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

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

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

/**
* Получение haveCertificate от пользователя
* @param {object} user - данные пользователя (значение по умолчанию = {}).
* @returns {Boolean}
*/
const haveCertificate = async (user = {}) => {
try {
const {certificate = []} = user;
if(certificate.length) {
return true;
}
return false;
}catch(err) {
//Обработка ошибки
}
};

8. Создавайте код, способный обрабатывать непредсказуемые объекты null

Непредсказуемый объект null возникает, когда есть вложенный объект данных, в котором иногда ключ вложенного объекта равен null или изменен. Поэтому, если попытаться получить доступ к ключу вложенного объекта, получим результат undefined. Это может привести к аварийному сбою кода. Если вы когда-нибудь сталкивались с подобным случаем, то могли получить ошибку типа “Cannot read property ‘data’ of undefined” (“Невозможно прочитать свойство ‘data’ из неопределенного значения”)’.

Решение этой проблемы заключается в том, чтобы сначала проверить объект, прежде чем обращаться к более глубокому вложенному объекту. Для этого можно использовать логический оператор OR (||), опциональную цепочку и оператор слияния с нулевым значением (??):

const user = {
name: 'Dafa',
gender: 'Male',
age: 23
};

// Использование логического оператора OR
const city = ((user.address || {}).city || 'city not yet defined');
// Использование опциональной цепочки
const postalCode = user?.address?.postalCode || 'postal code not yet defined';
// Использование оператора слияния с нулевым значением (??)
const hobbies = user.hobbies ?? 'users have no hobbies';


console.info(`${user.name} \n\n${city}\n${postalCode}\n${hobbies}\n`)

Можно обрабатывать непредсказуемые объекты null в параметрах функций. Просто добавьте значение по умолчанию в параметр:

//Значением по умолчанию пользовательских параметров является object {}
const haveCertificate = async (user = {}) => {
try {
//переменная certificate имеет значение по умолчанию []
const {certificate = []} = user;
if(certificate.length) {
return true;
}
return false;
}catch(err) {
//Обработка ошибки
}
};

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Dafa Pramudya: 8 Simple Tips that Make Your Javascript Code More Readable & Clean Code

Предыдущая статьяМифы Go, в которые мы верим: емкость
Следующая статьяРазмеченные объединения в TypeScript