Najbardziej przyjazny dla człowieka sposób zamawiania definicji metod klasowych?

38

W dowolnej definicji klasy widziałem definicje metod uporządkowane na różne sposoby: alfabetyczne, chronologiczne oparte na najczęstszym użyciu, alfabetycznie pogrupowane według widoczności, alfabetyczne z pogrupowanymi i ustawiającymi pogrupowanymi itd. Kiedy zaczynam pisać nową klasę, Zwykle wpisuję wszystko, a potem zmieniam kolejność, kiedy skończę pisać całą klasę. W tej sprawie mam trzy pytania:

  1. Czy zamówienie ma znaczenie?
  2. Czy istnieje zamówienie „najlepsze”?
  3. Zgaduję, że nie ma, więc jakie są zalety i wady różnych strategii porządkowania?
Johntron
źródło
1
Tak naprawdę nie oczekujesz, że ludzie będą wycinać / wklejać kod po prostu w celu zmiany kolejności metod. Jeśli IDE zrobi to automatycznie, to dobrze. W przeciwnym razie trudno jest to wyegzekwować.
Reactgular
Aby wyjaśnić, moje pytanie dotyczy czytelności / użyteczności, a nie składni.
Johntron

Odpowiedzi:

52

W niektórych językach programowania kolejność ma znaczenie, ponieważ nie można używać rzeczy, dopóki nie zostaną zadeklarowane. Ale z wyjątkiem tego, w większości języków nie ma to znaczenia dla kompilatora. Zatem pozostaje ci to ważne dla ludzi.

Mój ulubiony cytat Martina Fowlera to: Any fool can write code that a computer can understand. Good programmers write code that humans can understand.Powiedziałbym, że porządek w twojej klasie powinien zależeć od tego, co ułatwia ludziom zrozumienie.

Osobiście wolę łagodniejsze traktowanie, które Bob Martin podaje w swojej Clean Codeksiążce. Zmienne składowe na początku klasy, następnie konstruktory, a następnie wszystkie inne metody. I zamawiasz metody, aby były blisko siebie i jak są używane w klasie (zamiast arbitralnie umieszczać wszystkie publiczne, a następnie prywatne, a następnie chronione). Nazywa to minimalizowaniem „odległości w pionie” lub coś w tym rodzaju (w tej chwili nie mam przy sobie książki).

Edytować:

Podstawową ideą „odległości w pionie” jest to, że chcesz uniknąć skakania wokół kodu źródłowego, aby go zrozumieć. Jeśli rzeczy są powiązane, powinny być bliżej siebie. Niepowiązane rzeczy mogą być dalej od siebie.

Rozdział 5 Czystego kodu (świetna książka, btw) zawiera mnóstwo szczegółów na temat tego, jak Pan Martin sugeruje zamówienie kodu. Sugeruje, że czytanie kodu powinno działać tak, jak czytanie artykułu z gazety: na pierwszym miejscu są szczegóły wysokiego poziomu (u góry), a podczas czytania pojawia się więcej szczegółów. Mówi: „Jeśli jedna funkcja wywołuje inną, powinny one być pionowo zamknięte, a osoba dzwoniąca powinna znajdować się powyżej rozmówcy, jeśli to w ogóle możliwe”. Ponadto powiązane pojęcia powinny być blisko siebie.

Oto wymyślony przykład, który jest zły na wiele sposobów (zły projekt OO; nigdy nie używaj doublepieniędzy), ale ilustruje ideę:

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() { ... }
}

Metody są uporządkowane, aby były zbliżone do tych, które je wywołują, schodząc z góry. Gdybyśmy umieścili wszystkie privatemetody poniżej publictych, musiałbyś robić więcej skoków, aby śledzić przebieg programu.

getFirstNamei getLastNamesą ze sobą powiązane (i getEmployeeIdprawdopodobnie też są), więc są blisko siebie. Moglibyśmy przenieść je wszystkie na dół, ale nie chcielibyśmy widzieć getFirstNamena górze i getLastNamena dole.

Mam nadzieję, że daje to podstawowy pomysł. Jeśli jesteś zainteresowany tego typu rzeczami, zdecydowanie polecam lekturę Clean Code.

Allan
źródło
Muszę wiedzieć, w jaki sposób należy ustawić obiekty ustawiające i pobierające zmienne instancji. Czy powinno przyjść zaraz za konstruktorem klasy, czy na końcu klasy?
srinivas
Osobiście lubię je u góry po konstruktorze. Ale to tak naprawdę nie ma znaczenia; Polecam spójność w projekcie i ze swoim zespołem jako dobry sposób na decyzję.
Allan
Nie powinien calculateBonus()przyjść wcześniej isFullTimeEmployee()i didCompleteBonusObjectives()?
winklerrr,
@winklerrr Widzę argument za tym. Umieściłem je tam, gdzie to zrobiłem, isFullTimeEmployeei didCompleteBonusObjectivessą używane przez, isEligibleForBonuswięc powinny być blisko niego pionowo. Ale możesz potencjalnie przejść w calculateBonusgórę, aby zbliżyć go do miejsca, w którym się nazywa. Niestety, ponieważ masz funkcje wywołujące funkcje, w końcu możesz mieć problemy (takie jak funkcje wspólne wywoływane przez wiele innych), w których nie ma idealnego uporządkowania. Zatem decyzja należy do Ciebie. Polecam utrzymywanie małych klas i funkcji w celu złagodzenia tych problemów.
Allan
2

Generalnie moje metody zamawiam według relacji i kolejności użycia.

Weź udział w zajęciach sieciowych, zgrupuję wszystkie metody TCP razem, a następnie wszystkie metody UDP razem. Wewnątrz TCP miałbym metodę instalacji jako pierwszą, może wyślę daną wiadomość jako drugą i zamknę gniazdo tcp jako trzecie.

Oczywiście nie wszystkie klasy będą pasować do tego wzorca, ale taki jest mój ogólny tok pracy.

Robię to w ten sposób, aby debugować bardziej niż cokolwiek innego, kiedy mam problem i chcę przejść do metody, nie sądzę, jak to się przeliteruje, myślę, za co jest odpowiedzialny i przejdź do tej sekcji.

W szczególności ma to sens, gdy osoba trzecia przegląda / używa twojego kodu jako zgrupowanego i będzie postępować zgodnie z kolejnością jego używania, więc kod, który będą pisać z twoją klasą, będzie miał taką samą strukturę jak klasa.

Czy to ma znaczenie?

zdecydowanie dla czytelności.

poza tym niezupełnie, tylko w przypadkach, w których w niektórych językach nie można wywołać metody, chyba że zdefiniowano powyżej, gdzie jest wywoływana itp.

Simon McLoughlin
źródło
0

Idealnie, twoje zajęcia są tak małe, że to nie ma znaczenia. Jeśli masz tylko kilkanaście metod, a Twój edytor lub IDE obsługuje składanie, nie masz problemu, ponieważ cała lista metod mieści się w 12 liniach.

W przeciwnym razie rozróżnienie najwyższego poziomu powinno być publiczne i prywatne. Najpierw wypisz metody publiczne: są to, czego ludzie będą najbardziej szukać, ponieważ określają one sposób, w jaki twoja klasa łączy się z resztą bazy kodu.

Następnie w każdym z nich najbardziej sensowne jest grupowanie metod według funkcjonalności: konstruktory i destruktory w jednym bloku, gettery / settery w innym, przeciążenie operatora, metody statyczne i reszta. W C ++ lubię być operator=blisko konstruktorów, ponieważ jest to ściśle związane z konstruktorem kopiowania, a także dlatego, że chcę móc szybko wykryć, czy wszystkie (lub żadne) domyślne ctor, copy ctor, operator = i dtor istnieć.

tdammers
źródło
-1

tl; dr

Tylko jeśli język, w którym pracujesz, wymaga określonego zamówienia. Poza tym zamówienie zależy od Ciebie. Wybierz system, który jest spójny i ma sens, i staraj się trzymać go tak bardzo, jak to możliwe.


1. Czy zamówienie ma znaczenie?

Tylko jeśli język, w którym pracujesz, musi mieć wcześniej zdefiniowaną funkcję w pliku niż tam, gdzie jest ona wywoływana, tak jak w tym przykładzie:

void funcA()
{
   funcB();
}

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

pojawi się błąd, ponieważ zadzwonisz funcB()przed użyciem. Myślę, że jest to problem w PL / SQL i ewentualnie również w C, ale możesz mieć deklaracje przekazywania, takie jak:

void funcB();

void funcA()
{
   funcB();
}

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

To jedyna sytuacja, w której mogę wymyślić, gdzie, jeśli porządek jest „zły”, nie będziesz nawet w stanie skompilować.

W przeciwnym razie zawsze możesz zamówić je ponownie, jak chcesz. Prawdopodobnie możesz nawet napisać narzędzie, które zrobi to za ciebie (jeśli nie możesz go znaleźć).

2) Czy istnieje zamówienie „najlepsze”?

Jeśli język / środowisko nie ma wymagań dotyczących zamawiania, wówczas „najlepsza” kolejność jest najlepsza dla Ciebie . Dla mnie lubię mieć wszystkie funkcje pobierające / ustawiające razem, zwykle na początku klasy (ale po konstruktorach / inicjalizatorach statycznych), a następnie metody prywatne, następnie chronione, a następnie publiczne. W każdej grupie opartej na zakresie zwykle nie ma uporządkowania, chociaż przeciążone metody staram się trzymać razem, w kolejności według liczby parametrów. Staram się także trzymać metody razem z powiązaną funkcjonalnością, chociaż czasami muszę przerwać porządkowanie oparte na zakresie; a czasem próba utrzymania kolejności opartej na zakresie powoduje podział grupy według funkcji. A moje IDE może dać mi alfabetyczny zarys, więc to też dobrze. ;)

Niektóre języki, takie jak C #, mają możliwość grupowania kodu w „regionach” , które nie mają wpływu na kompilację, ale mogą ułatwić utrzymanie powiązanych funkcji razem, a następnie ukryć / wyświetlić je za pomocą IDE. Jak zauważył MainMa , są tacy, którzy uważają to za złą praktykę. Widziałem dobre i złe przykłady regionów używanych w ten sposób, więc jeśli zamierzasz pójść tą drogą, bądź ostrożny.

FrustratedWithFormsDesigner
źródło