Как разделяй и властвуй уменьшить появление классов монстров

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

Для создания эффективного решения создается множество классов. Сначала может показаться, что небольшого количества классов достаточно, так как у вас не будет много файлов, висящих повсюду. Это приводит к тому, что иногда называют божественными объектами или, как я предпочитаю, классами монстров. Технически, это классы, которые знают и делают слишком много. Я предпочитаю идею, что это чертовски большой класс. Этот антипаттерн встречается чаще, чем вы думаете, он даже появляется в некоторых замечательных играх, таких как Celeste. Этот анти-шаблон меня больше всего раздражает по множеству причин. Но, может быть, нам стоит проанализировать кейс, чтобы лучше понять эту тему?

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

С этим не по пути, давайте посмотрим на тематическое исследование.

Типичный автомобильный пример

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

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

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

Car.cs

public class Car
{
   private float CurrentSpeed;
   private float MaxSpeed;
   private float AccelerationRate;
   private float TurnSpeed;
   private float MaxPetrol;
   private float CurrentPetrol;
   private int Seats;
   private int Wheels;
   private string ModelName;
   private string PrimaryColour;
   public void Accelerate() { }
   public void Brake() { }
   public void Reverse() { }
   public void RefillPetrol() { }
   public void TurnCar() { }
}

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

Как вы, наверное, посчитали, есть 10 переменных и 5 методов. В данном случае это проблема, потому что этот класс имеет более одной ответственности (см. SRP), и этот класс потенциально может стать чертовски большим.

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

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

Car.cs

public class Car
{
    private Engine CarEngine;
    private CarBody BodyOfCar;
    private FuelSystem CarFuel;
    private WheelSystem CarWheels;
public void DriveCar() { }
}

WheelSystem.cs

public class WheelSystem {
private float TurnSpeed;
    private int Wheels;
public void Brake() { }
public void Turn() { }
}

Engine.cs

public class Engine
{
    private float CurrentSpeed;
    private float MaxSpeed;
    private float AccelerationRate;
    public void Accelerate() { }
    public void Reverse() { }
}

CarBody.cs

public class CarBody
{
    private string ModelName;
    private string PrimaryColour;
    private int Seats;
public void ChangeColour() { }
    public void ChangeModelName(string GivenName) { }
}

Топливная система.cs

public class FuelSystem
{
    private float MaxPetrol;
    private float CurrentPetrol;
public void RefillPetrol() { }
}

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

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

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

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

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

Заключительные комментарии

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

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

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

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

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

Увидимся!