Самый удобный способ упорядочить определения методов класса?

37

В любом заданном определении класса я видел определения методов, упорядоченные по-разному: алфавитный, хронологический, основанный на наиболее распространенном использовании, алфавитный, сгруппированный по видимости, алфавитный с геттерами и сеттерами, сгруппированными вместе, и т. Д. Когда я начинаю писать новый класс, Я имею тенденцию просто вводить все, а затем переупорядочивать, когда я закончу писать весь класс. На этой ноте у меня есть три вопроса:

  1. Имеет ли значение порядок?
  2. Есть ли «лучший» заказ?
  3. Я предполагаю, что нет, так каковы плюсы и минусы различных стратегий заказа?
Johntron
источник
1
Вы действительно не ожидаете, что люди будут вырезать / вставлять код просто для того, чтобы изменить порядок методов. Если IDE делает это автоматически, тогда нормально. В противном случае это трудно осуществить.
Reactgular
Чтобы уточнить, мой вопрос касается удобочитаемости / удобства использования, а не синтаксиса.
Johntron
1
Связанные: programmers.stackexchange.com/questions/186418/…
Энтони Пеграм

Ответы:

51

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

Моя любимая цитата Мартина Фаулера такова: Any fool can write code that a computer can understand. Good programmers write code that humans can understand.так что я бы сказал, что порядок в вашем классе должен зависеть от того, что облегчает людям понимание.

Я лично предпочитаю постепенное обращение, которое Боб Мартин дает в своей Clean Codeкниге. Переменные-члены в начале класса, затем конструкторы, а затем все остальные методы. И вы приказываете, чтобы методы были близки к тому, как они используются в классе (вместо того, чтобы произвольно выставлять все общедоступные, а затем закрытые, а затем защищенные). Он называет это минимизацией «вертикального расстояния» или чего-то в этом роде (на данный момент у меня нет книги).

Редактировать:

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

Глава 5 Чистого кода (отличная книга, кстати) подробно описывает, как мистер Мартин предлагает заказывать код. Он предлагает, чтобы чтение кода работало так же, как чтение газетной статьи: подробности высокого уровня идут первыми (вверху), и вы получаете больше деталей, когда читаете. Он говорит: «Если одна функция вызывает другую, они должны быть расположены вертикально, а вызывающая сторона должна быть выше вызываемой, если это вообще возможно». Кроме того, связанные понятия должны быть близко друг к другу.

Итак, вот надуманный пример, который во многих отношениях плох (плохой дизайн ОО; никогда не используется doubleза деньги), но иллюстрирует идею:

public class Employee {
  ...
  public String getEmployeeId() { return employeeId; }
  public String getFirstName() { return firstName; }
  public String getLastName() { return lastName; }

  public double calculatePaycheck() {
    double pay = getSalary() / PAY_PERIODS_PER_YEAR;
    if (isEligibleForBonus()) {
      pay += calculateBonus();
    }
    return pay;
  }

  private double getSalary() { ... }

  private boolean isEligibleForBonus() {
    return (isFullTimeEmployee() && didCompleteBonusObjectives());
  }

  public boolean isFullTimeEmployee() { ... }
  private boolean didCompleteBonusObjectives() { ... }
  private double calculateBonus() { ... }
}

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

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

Надеюсь, это даст вам основную идею. Если вы заинтересованы в таких вещах, я настоятельно рекомендую прочитать Clean Code.

Аллан
источник
Мне нужно знать, как сеттеры и геттеры переменных экземпляра должны быть размещены. Должен ли он идти сразу после конструктора класса или в нижней части класса?
Сринивас
Лично мне они нравятся сверху после конструктора. Но это не имеет значения; Я бы порекомендовал последовательность в вашем проекте и с вашей командой, как хороший способ принять решение.
Аллан
Не должен calculateBonus()прийти раньше isFullTimeEmployee()и didCompleteBonusObjectives()?
winklerrr
@winklerrr Я вижу аргумент для этого. Я поместил их туда , где я сделал , потому isFullTimeEmployeeи didCompleteBonusObjectivesиспользуются isEligibleForBonusтаким образом , они должны быть вертикально близко к нему. Но вы могли бы calculateBonusподняться, чтобы приблизить его к тому, где он называется. К сожалению, поскольку у вас есть функции, вызывающие функции, в конечном итоге вы сталкиваетесь с проблемами (например, одна общая функция, вызываемая несколькими другими), где нет идеального упорядочения. Тогда это остается на ваше усмотрение. Я рекомендую держать классы и функции небольшими, чтобы смягчить эти проблемы.
Аллан
2

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

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

Очевидно, что не все классы будут соответствовать этому шаблону, но это мой общий рабочий процесс.

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

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

Что касается это имеет значение?

для читабельности, безусловно.

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

Саймон Маклафлин
источник
0

В идеале, ваши классы настолько малы, что это не имеет значения. Если у вас есть только дюжина методов, и ваш редактор или IDE поддерживает свертывание, у вас нет проблем, потому что весь список методов помещается в 12 строк.

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

Затем внутри каждого из них имеет смысл сгруппировать методы по функциональности: конструкторы и деструкторы в одном блоке, геттеры / сеттеры в другом, перегрузки операторов, статические методы и остальная часть группы. В C ++ я предпочитаю быть operator=ближе к конструкторам, потому что он тесно связан с конструктором копирования, а также потому, что я хочу иметь возможность быстро определить, все ли (или ни один) из ctor по умолчанию, copy ctor, operator = и dtor существует.

tdammers
источник
-1

ТЛ; др

Только если язык, на котором вы работаете, требует определенного заказа. Кроме этого, заказ зависит от вас. Выберите систему, которая является последовательной и имеет смысл, и старайтесь придерживаться ее как можно больше.


1 Имеет ли значение порядок?

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

void funcA()
{
   funcB();
}

void funcB()
{
   //do something interesting...
}

вы получите ошибку, потому что вы звоните, funcB()прежде чем использовать его. Я думаю, что это проблема в PL / SQL и, возможно, в C, но вы можете иметь предварительные объявления, такие как:

void funcB();

void funcA()
{
   funcB();
}

void funcB()
{
   //do something interesting...
}

Это единственная ситуация, о которой я могу подумать, когда, если порядок «неправильный», вы даже не сможете скомпилировать.

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

2 Есть ли «лучший» заказ?

Если язык / окружающая среда не приказывая требования, то «лучший» порядок является тот , который лучше работает для вас . Для меня мне нравится собирать все получатели / установщики вместе, обычно в начале класса (но после конструкторов / статических инициализаторов), а затем закрытые методы, затем защищенные, а затем публичные. В каждой группе, основанной на области действия, обычно нет упорядочения, хотя перегруженные методы я стараюсь хранить вместе в порядке количества параметров. Я также пытаюсь объединить методы со связанной функциональностью, хотя иногда мне приходится нарушать порядок, основанный на области действия; и иногда пытаясь поддерживать упорядочение на основе области действия, групповые по функциональности. И моя IDE может дать мне буквенное представление схемы, так что это тоже хорошо. ;)

Некоторые языки, например C #, имеют возможность группировать код в «регионах» , которые не влияют на компиляцию, но могут упростить объединение связанных функций, а затем скрыть / отобразить их с помощью IDE. Как указала MainMa , есть люди, которые считают это плохой практикой. Я видел хорошие и плохие примеры использования регионов таким образом, поэтому, если вы собираетесь пойти по этому пути, будьте осторожны.

FrustratedWithFormsDesigner
источник