Как стать автором
Обновить

Общепринятые принципы разработки

Уровень сложностиПростой

Всем привет! Это моя первая публикация, поэтому прошу строго не судить :)
Ниже будут представлены примеры с использованием JavaScript и библиотеки InversifyJS.

KISS (Keep It Simple, Stupid - Сделай это проще) 

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

Плохой пример
function calculateSum(array) {
  let sum = 0;
  for (let i = 0; i < array.length; i++) {
    sum += array[i];
  }
  return sum;
}

Хороший пример
const calculateSum = array => array.reduce((sum, curr) => sum + curr, 0);

DRY (Don't Repeat Yourself - Не повторяй себя)

Принцип, который подразумевает избегание повторения кода.

Плохой пример
// Каждый раз для нового класса, работающего со списком,
// реализуется похожий набор свойств и методов.
@injectable()
class UserStore {
  list = [];
 
  // Реализация методов и свойств для работы со списком.
  add() {};
  delete() {};
  edit() {};
  // и т.д.
}
 
@injectable()
class TaskStore {
  list = [];
 
  // Реализация методов и свойств для работы со списком.
  add() {};
  delete() {};
  edit() {};
  // и т.д.
}

Хороший пример
// Создаем базовый интерфейс для работы со списком.
interface ListStoreInterface<T> {
  list: T[];
 
  add(item: T): void;
  delete(item: T['id']): void;
  edit(item: Partial<T>): T;
  // и т.д.
}
 
// Создаем идентификатор для интерфейса.
export const ListStoreInterface: interfaces.ServiceIdentifier<ListStoreInterface> =
  Symbol('ListStoreInterface');
 
// Создаем базовый класс реализующий свойства и методы интерфейса.
class ListStore<T> implements ListStoreInterface<T> {
  // Реализация методов и свойств для работы со списком.
}
 
// Выполняем привязку идентификатора к классу.
ctx.bind(ListStoreInterface).to(ListStore).inSingletonScope();
 
// Если в классе требуется работа со списком,
// просто выполняем инъекцию без дополнительного описания методов и свойств.
@injectable()
class UserStore {
  @inject(ListStoreInterface)
  listStore: ListStoreInterface;
   
  // Код...
}
 
@injectable()
class TaskStore {
  @inject(ListStoreInterface)
  listStore: ListStoreInterface;
 
  // Код...
}

Single Responsibility Principle (Принцип единственной ответственности)  

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

Плохой пример
// Вся логика перемешена и размещена в одном классе.
@injectable()
export class UserService {
  list = [];
 
  // Реализация методов и свойств для работы со списком.
  add(){};
  delete(){};
  edit(){};
  // и т.д.
 
  async getList() {
    // Запрос на сервер для получения данных.
    // Маппинг полей.
    // и т.д.
  }
}

Хороший пример
// Логика декомпозирована и вынесена в отдельные модули.
@injectable()
export class UserService {
  // Модуль для работы со списком.
  @inject(UserListInterface)
  listStore: UserListInterface;
 
  // Модуль маппинга полей.
  @inject(UserRegisterInterface)
  registerEntity: UserRegisterInterface;
 
  // Модуль получения данных с сервера.
  @inject(UserRequestGetListInterface)
  requestGetList: UserRequestGetListInterface;
 
  // Метод регистрации элемента.
  register(){};
 
  // Метод получения готового списка.
  async getList() {}
}

Open/Closed Principle (Принцип открытости/закрытости)

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

Плохой пример
// Для добавления новой операции необходимо вносить изменения в основной код функции.
function calculateSum(array, operation) {
  let sum = 0;
  for (let i = 0; i < array.length; i++) {
    if (operation === 'add') {
      sum += array[i];
    } else if (operation === 'subtract') { // Добавляем новую операцию.
      sum -= array[i];
    }
  }
  return sum;
}
 
calculateSum([1, 2, 3], 'add');
calculateSum([1, 2, 3], 'subtract');

Хороший пример
// Добавление новой операции осуществляется без изменения основного кода функции.
function calculateSum(array, calculator) {
  return array.reduce(calculator, 0);
}
 
function add(acc, curr) {
  return acc + curr;
}
 
function subtract(acc, curr) { // Добавляем новую операцию.
  return acc - curr;
}
 
calculateSum([1, 2, 3], add);
calculateSum([1, 2, 3], subtract);

Liskov Substitution Principle (принцип подстановки Лисков)

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

Базовый класс
export class List {
  _list = [];
   
  setList(list) {
    this._list = list;
  }
 
  get list() {
    return this._list;
  }
}

Плохой пример
// Базовый класс List не может быть заменен дочерним классом FilterList,
// т.к. переопределен геттер нарушающий логику программы.
export class FilterList extends List {
  filter = {};
 
  constructor() {
    super();
  }
   
  setFilter(filter) {
    this.filter = filter;
  }
   
  get list() {
    return this._list.map(this.filter);
  }
}

Хороший пример
// Базовый класс List может быть заменен дочерним классом FilterList
// без нарушения логики программы.
export class FilterList extends List {
  filter = {};
 
  constructor() {
    super();
  }
   
  setFilter(filter) {
    this.filter = filter;
  }
   
  get filteredList() {
    return this._list.map(this.filter);
  }
}

Interface Segregation Principle (Принцип разделения интерфейса)

Сущности не должны зависеть от методов, которые они не используют. Это означает, что интерфейсы должны быть максимально простыми и содержать только базовый набор методов и свойств.

Плохой пример
// Базовый интерфейс содержит свойства и методы, которые могут не потребоваться.
interface User {
  id: number;
  name: string;
  email: string;
  address: string;
  phone: string;
  age: number;
  gender: string;
  friends: User[];
  addFriend(friend: User): void;
}
 
class User implements User {
  // Код...
}

Хороший пример
// Выполнена декомпозиция интерфейса.
interface User {
  id: number;
  name: string;
  email: string;
}
 
interface Profile {
  address: string;
  phone: string;
  age: number;
  gender: string;
}
 
interface Friendable {
  friends: User[];
  addFriend(friend: User): void;
}
 
class UserProfile implements User, Profile {
  // Код...
}
 
class UserFriendable implements User, Friendable {
  // Код...
}

Dependency Inversion Principle (Принцип инверсии зависимостей)

Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Вместо этого они должны зависеть от абстракций.

Плохой пример
// Верхнеуровневый модуль StorageManager зависит от реализации
// нижнеуровнего модуля LocalStorage.
// Если потребуется заменить LocalStorage на другое хранилище,
// верхнеуровневый модуль придется переписывать.
class StorageManager {
  storage: LocalStorage;
   
  constructor() {
    this.storage = new LocalStorage();
  }
   
  setItem({key, value}) {
    // Код...
    this.storage.setItem({key, value});
  }
   
  getItem(key) {
    // Код...
    this.storage.getItem(key);
  }
}
 
class LocalStorage {
  setItem({key, value}) {
    localStorage.setItem(key, value);
  }
 
  getItem(key) {
    localStorage.getItem(key);
  }
 
  // и т.д.
}

Хороший пример
// Создаем базовый интерфейс хранилища.
interface StorageInterface {
  set: (data) => void;
  get: (data) => any;
  // и т.д.
}
 
// Создаем одноименный идентификатор для интерфейса.
export const StorageInterface: interfaces.ServiceIdentifier<StorageInterface> =
  Symbol('StorageInterface');
 
// Привязываем индентификатор к классу с методами описанными в интерфейсе.
ctx.bind(StorageInterface).to(LocalStorage).inSingletonScope();
 
class StorageManager {
  // Выполняем инъекцию зависимости.
  // В качестве инъектируемой сущности используется абстрактный интерфейс,
  // а не конкретный класс.
  @inject(StorageInterface)
  storage: StorageInterface;
 
  set(data) {
    // Код...
    this.storage.set(data);
  }
 
  get(data) {
    // Код...
    return this.storage.get(data);
  }
}
 
// Реализуем методы описанные в интерфейсе
class LocalStorage implements StorageInterface {
  set({key, value}) {
    localStorage.setItem(key, value);
  }
 
  get(key) {
    localStorage.getItem(key);
  }
  // и т.д.
}

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.