Наткнувшись на материал по принципам чистый код для TypeScript и прочитав его решил взяться за его перевод. Здесь я хочу поделиться с вами некоторыми выдержками из этого перевода, так как некоторые моменты чистого кода для TypeScript повторяют такие же принципы для JavaScript, я их здесь описывать не буду, если будет интересно перевод для JS уже публиковался на хабре(@BoryaMogila) или же можете ознакомится с ними в первоисточнике.
Для начала давайте же разберемся что такое, эти принципы чистого кода. Но дать четкого определения чистого кода к сожалению вряд ли получится. Отчасти это все зависит от людей, так например приходишь в музей рассматриваешь картину и думаешь что за уродство, но тут же подходит другой человек и говорит, какое великолепие. Да у нас есть какие определенные, общие черты мира, где мы можем сказать, что то или иное красиво, но дать всему этому определение мы точно не сможем. Так и здесь это всего лишь какие то небольшие критерии этой красоты, соблюдение которых выбирает сам, так как. это не те правила которые высечены в граните. Это просто рекомендации.
Переменные
Используйте enum для документирования
Enam'ы могут помочь документированию вашего кода. Например когда мы обеспокоены тем, что наши переменные отличаются от значений.
Плохо:
const GENRE = {
ROMANTIC: 'romantic',
DRAMA: 'drama',
COMEDY: 'comedy',
DOCUMENTARY: 'documentary',
}
projector.configureFilm(GENRE.COMEDY);
class Projector {
// delactation of Projector
configureFilm(genre) {
switch (genre) {
case GENRE.ROMANTIC:
// some logic to be executed
}
}
}
Хорошо:
enum GENRE {
ROMANTIC,
DRAMA,
COMEDY,
DOCUMENTARY,
}
projector.configureFilm(GENRE.COMEDY);
class Projector {
// delactation of Projector
configureFilm(genre) {
switch (genre) {
case GENRE.ROMANTIC:
// some logic to be executed
}
}
}
Функции
Избегайте проверки типов
TypeScript является надмножеством синтаксиса JavaScript и добавляют дополнительные статические проверки типов для языка. Всегда предпочитайте указывать типы переменных, параметров и возвращаемых значений, чтобы использовать всю мощь TypeScript. Это делает будущий рефакторинг более легким.
Плохо:
function travelToTexas(vehicle: Bicycle | Car) {
if (vehicle instanceof Bicycle) {
vehicle.pedal(currentLocation, new Location('texas'));
} else if (vehicle instanceof Car) {
vehicle.drive(currentLocation, new Location('texas'));
}
}
Хорошо:
type Vehicle = Bicycle | Car;
function travelToTexas(vehicle: Vehicle) {
vehicle.move(currentLocation, new Location('texas'));
}
Используйте итераторы и генераторы
Используйте генераторы и итераторы при работе с коллекциями данных, которые используются как поток.
Есть несколько причин для этого:
- отделяет вызываемый объект от реализации генератора в том смысле, что вызываемый объект решает сколько элементов
иметь для доступа - ленивое выполнение, элементы передаются по требованию
- встроенная поддержка итерации элементов с использованием синтаксиса
for-of
- итераторы позволяют реализовать оптимизированные паттерны итераторов
Плохо:
function fibonacci(n: number): number[] {
if (n === 1) return [0];
if (n === 2) return [0, 1];
const items: number[] = [0, 1];
while (items.length < n) {
items.push(items[items.length - 2] + items[items.length - 1]);
}
return items;
}
function print(n: number) {
fibonacci(n).forEach(fib => console.log(fib));
}
// Print first 10 Fibonacci numbers.
print(10);
Хорошо:
// Generates an infinite stream of Fibonacci numbers.
// The generator doesn't keep the array of all numbers.
function* fibonacci(): IterableIterator<number> {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
function print(n: number) {
let i = 0;
for (const fib of fibonacci()) {
if (i++ === n) break;
console.log(fib);
}
}
// Print first 10 Fibonacci numbers.
print(10);
Существуют библиотеки, которые позволяют работать с итераторами так же, как и с собственными массивами, путем цепочки методов, таких как map
, slice
, forEach
и др. Смотрите itiriri пример продвинутой манипуляции с итераторами (или itiriri-async для манипуляции с асинхронными итераторами).
import itiriri from 'itiriri';
function* fibonacci(): IterableIterator<number> {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
itiriri(fibonacci())
.take(10)
.forEach(fib => console.log(fib));
Объекты и структуры данных
Используйте геттеры и сеттеры
TypeScript поддерживает синтаксис геттеров и сеттеров. Использовать геттеры и сеттеры для доступа к данным объекта гораздо лучше, чем напрямую обращаться к его свойствам. "Почему?" спросите вы. Вот список причин:
- Если вы хотите реализовать больше, чем просто доступ к свойству, вам нужно поменять реализацию в одном месте, а не по всему коду
- Валидацию легко реализовать на уровне реализации
set
- Инкапсуляция внутреннего состояния
- Легко добавить логирование и обработку ошибок на уровне геттеров и сеттеров
- Вы можете лениво подгружать свойства вашего объекта, например, с сервера
Плохо:
type BankAccount = {
balance: number;
// ...
}
const value = 100;
const account: BankAccount = {
balance: 0,
// ...
};
if (value < 0) {
throw new Error('Cannot set negative balance.');
}
account.balance = value;
Хорошо:
class BankAccount {
private accountBalance: number = 0;
get balance(): number {
return this.accountBalance;
}
set balance(value: number) {
if (value < 0) {
throw new Error('Cannot set negative balance.');
}
this.accountBalance = value;
}
// ...
}
// Теперь `BankAccount` инкапсулирует логику проверки.
// Если однажды спецификации изменятся, и нам понадобится дополнительное правило проверки,
// нам придется изменить только реализацию `сеттера`,
// оставив весь зависимый код без изменений.
const account = new BankAccount();
account.balance = 100;
Создавайте объекты с приватными/защищенными полями
TypeScript поддерживает public
(по умолчанию), protected
и private
средства доступа к свойствам класса.
Плохо:
class Circle {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
perimeter() {
return 2 * Math.PI * this.radius;
}
surface() {
return Math.PI * this.radius * this.radius;
}
}
Хорошо:
class Circle {
constructor(private readonly radius: number) {
}
perimeter() {
return 2 * Math.PI * this.radius;
}
surface() {
return Math.PI * this.radius * this.radius;
}
}
Уважаемые читатели, а какими принципами вы пользуетесь при использовании TypeScript?
Продолжение:
Вторая часть
Третья часть
Полный перевод