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

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

Лучшие практики

1. Отступ

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

Некоторые языки программирования, такие как C, Java, Javascript и т. д., могут не нуждаться в правильном отступе. В отличие от Python, основанного на отступах, эти языки программирования будут нормально работать в любой форме отступов. Но рассмотрим следующий пример

  async function uploadImage(type) {
  const identifier = v4();
  const user = localStorage.getItem('user');
  const promises = [];
  for (let i = 1; i < selectedFiles.length + 1; i++) { const storageRef = ref(storage, `images/${groupId}/${user}/${type}/${identifier}/${i}`);
  promises.push(uploadBytes(storageRef, selectedFiles[i - 1]).then(() => {
  setProgress((progress) => progress + 1)}))}
  setProgress(0);
  return Promise.all(promises).then(() => { return `images/${groupId}/${user}/${type}/${identifier}`});};

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

async function uploadImage(type) {
    const identifier = v4();
    const user = localStorage.getItem('user');
    const promises = [];
    for (let i = 1; i < selectedFiles.length + 1; i++) {
      const storageRef = ref(storage, `images/${groupId}/${user}/${type}/${identifier}/${i}`);
      promises.push(uploadBytes(storageRef, selectedFiles[i - 1]).then(() => {
        setProgress((progress) => progress + 1)
      }))
    }
    setProgress(0);
    return Promise.all(promises).then(() => {
      return `images/${groupId}/${user}/${type}/${identifier}`
    });
  };

Намного лучше. Мы можем хорошо видеть код и знать, что делает каждая строка. Еще один совет для достижения хорошего отступа — это использование встроенной функции IDE. Нажав SHIFT + ALT + F в VSCode, он автоматически настроит ваш код на правильный отступ.

2. Именование переменных

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

async function funct(x) {
    if (x!= "") {
        const y= ref(storage, url);
        const list = []
        const z = await listAll(y)
        z.items.forEach((a) => {
            list.push(getDownloadURL(a))
        });
        return Promise.all(list)
    }
    else {
        return []
    }
  }

Пример выше очень плохой. Мы не знаем, что такое x, y, z и a. Мы также не знаем, для чего используется функция. Вместо этого мы можем написать это так

async function getPhotoOnListing(url) {
    if (url != "") {
        const imageRef = ref(storage, url);
        const imageList = []
        const response = await listAll(imageRef)
        response.items.forEach((item) => {
            imageList.push(getDownloadURL(item))
        });
        return Promise.all(imageList)
    }
    else {
        return []
    }
  }

Из самого названия функции мы знаем, что функция используется для получения фотографии для листинга. Описательные имена переменных дают нам понять, что переменная с именем imageRef будет хранить ссылку на изображение, где imageList будет хранить изображения.

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

3. Комментарий к коду

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

// Upload & Store images in firebase
async function uploadImage(type) {
    // Create unique image folder name
    const identifier = v4();
    const user = localStorage.getItem('user');
    const promises = [];
    // Iterate uploaded files
    for (let i = 1; i < selectedFiles.length + 1; i++) {
      // Get storage folder reference in firebase for the images
      const storageRef = ref(storage, `images/${groupId}/${user}/${type}/${identifier}/${i}`);
      // Save the photo in firebase using uploadBytes function from firebase
      promises.push(uploadBytes(storageRef, selectedFiles[i - 1]).then(() => {
        setProgress((progress) => progress + 1)
      }))
    }
  };

4. Не повторяйтесь

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

@require_POST
def create_group(request):
    if request.method == 'POST':
        user = is_authorized(request)
        if user and user.is_admin:
                deserialize = json.loads(request.body)
                group = Group(group_name=deserialize['name'], group_desc=deserialize['desc'], group_photo_profile_link=deserialize['photo_profile'])
                if not (Group.objects.filter(group_name=deserialize['name']).exists()):
                    group.save()
                    return JsonResponse({'message': 'Group created successfully'}, status=status.HTTP_201_CREATED)
                else:
                    return JsonResponse({'message': 'Group already exists'}, status=status.HTTP_400_BAD_REQUEST)
        else:
            return JsonResponse({"message": "Unauthorized"}, status=status.HTTP_401_UNAUTHORIZED)

@require_POST
def create_listing(request):
    if request.method == "POST":
        user = is_authorized(request)
        if user:
            user = User.objects.get(email=payload["id"])
            deserialize = json.loads(request.body)
            group = Group.objects.get(group_id=deserialize['group'])
            goods = Goods(goods_name=deserialize['name'], goods_price=deserialize['price'], goods_description=deserialize['desc'], goods_image_link=deserialize['image'], goods_region=deserialize['region'], goods_group_origin=group, goods_seller=user)
            goods.save()
            return JsonResponse({"message": "Listing created successfully"}, status=status.HTTP_201_CREATED)
        else:
            return JsonResponse({"message": "Unauthorized"}, status=status.HTTP_401_UNAUTHORIZED)

def is_authorized(request):
    token = request.headers.get("Authorization")
    if token and token.split(" ")[0] == "Bearer":
        token = token.split(" ")[1]
        try:
            payload = jwt.decode(token, "secret", algorithms=["HS256"])
            user = User.objects.get(email=payload["id"])
        except:
            return None
        return user
    else:
        return None

Как видите, я выделяю функцию аутентификации пользователя в отдельную. Другая функция может просто вызвать эту функцию. Имея это, мне нужно только изменить функцию is_authorized, если есть какие-либо изменения.

ТВЕРДЫЕ принципы

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

1. Принцип единой ответственности

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

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

2. Принцип «открыто-закрыто»

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

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

3. Принцип подстановки Лисков.

Принцип замещения Лисков гласит, что подкласс должен быть заменяемым для своего базового класса. Это означает, что если класс Y является подклассом класса X, то класс Y должен быть принят и работать правильно, когда функция ожидает объект класса X, поскольку они связаны. Если нет, это может привести к неожиданному поведению.

4. Принцип разделения интерфейса

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

Давайте использовать предыдущий пример символа. Основной класс имеет две функции: DealMeleeAttack() и DealRangedAttack(). Рыцарь не может атаковать на расстоянии, поэтому метод остается пустым. Чтобы соответствовать ISP, мы можем вместо этого создать новый класс интерфейса, MeleeCharacter & RangedCharacter.

5. Принцип инверсии зависимостей

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

Так ли важен и эффективен принцип SOLID?

Короткий ответ: да.

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

Согласно исследованию, проведенному Османом Тураном в 2018 году, реализация принципов проектирования SOLID действительно эффективна для программного обеспечения. Исследование заключает, что принципы проектирования SOLID повышают удобство сопровождения и гибкость кода. Нестабильное программное обеспечение имеет тенденцию увеличивать стоимость обслуживания до 75% от общих затрат. Следовательно, очень важно иметь стабильность, а сама стабильность очень связана со сцеплением. Наличие кода с низкой степенью связанности приводит к меньшим усилиям по внедрению новых технологий в программное обеспечение. Внедрив SOLID, мы добьемся низкой связанности, тем самым доказав эффективность.

Использованная литература:

  1. https://www.educative.io/blog/coding-best-practices
  2. https://www.educative.io/answers/what-are-the-solid-principles-in-java
  3. https://www.freecodecamp.org/news/solid-principles-explained-in-plain-english/
  4. https://dergipark.org.tr/en/download/article-file/1555528