Наткнувшись на материал по принципам чистый код для 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?
Продолжение следует...
miraage
Всё ниженаписанное подкреплено опытом написания немаленьких проектов с большим сроком поддержки и сменами с составе команды.
Я — евангелист строковых Enum. В статье, ключи отличаются от значений — я считаю это за абсолютно нереальный случай. Согласен, что так писать нельзя. И всё же:
1) Удобно дебажить (удачи в каких-нибудь React Devtools узнать значение в props, когда в Enum 20-30 значений)
2) Спокойно можно добавлять новые значения, менять местами (можно перзистить значения и спать спокойно, зная, что ничего не сломается)
3) Меньше размер бандла после компиляции (не катастрофа, но всё же)
Магические геттеры/сеттеры… Хочется вернуться в прошлое и не допустить их появление в JS.
Просто писать явно getSomething и setSomething.
Zoolander
Поддерживаю.
пп. 1 и 2 — бесценны.
Абсолютно непонятно, почему автор оригинального текста решил предпочесть числовые enum. Потом ему кто-то (или даже он сам) добавляет новый элемент в начало enum — и привет, все enum-проверки сломались.
JustDont
С учетом того, что в TS всё уже хорошо с discriminated unions, вам и строковые енумы нафиг не нужны. Зачем, когда то же самое сделает
Zoolander
покажите пример со switch или if
JustDont
Всё то же самое, что и с енумом. Да, у вас в коде явно будут написаны все эти 'foo' и 'bar' — но какая разница если это всё тайпчекается. Измените тип — компилятор этим сравнениям (и свичам) даст отлуп и потребует их поменять. Так же, как если вы значение енума переименуете.
Zoolander
Хорошо.Но есть нюанс
дает более полное представление о контексте применения кода, чем
Хотя бы потому, что романтическим может быть что угодно — от девушек до самолётов.
JustDont
Какое еще «более полное представление» вам нужно? Бороться с duck typing в JS/TS не имеет ни малейшего смысла.
Zoolander
я вас не критикую. Вы можете писать как угодно.
Обилие строковых литералов в коде на мой взгляд неудобно.
В последнем комментарии я имел в виду читабельность кода. Мы смотрим на коммит, там изменилась одна строчка. По выбранному enum мы видим и понимаем смысл кода быстрее, чем просто по строковому литералу.
Если вы читаете мало коммитов, для вас это может быть не актуально.
amakhrov
Умный поиск/рефакторинг в IDE гораздо лучше работает с явным enum, чем со строковыми константами.
Со строковыми литералами компилятор, конечно, выдаст ошибку, если рефакторинг был произведен некорректно. Но с enum рефакторинг просто сразу будет корректным.