Rozdział 5: Formatowanie
Rozdział 5: Formatowanie – Profesjonalna Komunikacja w Kodzie
O co chodzi w tej sekcji
Główną tezą Roberta C. Martina jest stwierdzenie, że formatowanie kodu służy komunikacji, a sprawna komunikacja jest najważniejszym obowiązkiem profesjonalnego programisty. Czytelność kodu ma bezpośredni wpływ na łatwość jego utrzymania i rozszerzania w przyszłości, ponieważ styl i dyscyplina zapisu przetrwają w projekcie znacznie dłużej niż konkretne fragmenty logiki biznesowej.
Kluczowe zasady i reguły
Formatowanie pionowe
- Metafora gazety: Plik źródłowy powinien przypominać artykuł prasowy – na samej górze znajduje się nagłówek (nazwa klasy) informujący o temacie, następnie algorytmy wysokiego poziomu, a im niżej schodzimy, tym więcej pojawia się szczegółów implementacyjnych.
- Odstępy pionowe: Myśli i koncepcje powinny być oddzielone pustymi wierszami. Każdy pusty wiersz to sygnał wzrokowy dla czytelnika, że zaczyna się nowa, odrębna koncepcja (np. nowa metoda lub grupa zmiennych).
- Gęstość pionowa: Wiersze kodu, które są ze sobą ściśle powiązane (np. zmienne instancyjne klasy), powinny znajdować się blisko siebie, bez zbędnych komentarzy czy pustych linii pomiędzy nimi.
- Zasada bliskości (Odległość pionowa):
- Deklaracje zmiennych: Powinny znajdować się jak najbliżej miejsca ich użycia.
- Zmienne instancyjne: W języku Java powinny być zgrupowane na początku klasy.
- Funkcje zależne: Jeśli jedna funkcja wywołuje inną, powinny znajdować się blisko siebie w pionie, przy czym funkcja wywołująca powinna być nad wywoływaną.
- Rozmiar pliku: Optymalna wielkość pliku to około 200 wierszy, przy czym absolutna górna granica powinna wynosić 500 wierszy.
Formatowanie poziome
- Długość wiersza: Należy unikać konieczności przewijania ekranu w prawo. Autor sugeruje limit 120 znaków na wiersz.
- Odstępy poziome: Używamy spacji do oddzielenia operatorów przypisania od operandów (w celu ich wyróżnienia) oraz po przecinkach w listach argumentów. Nie dodajemy spacji między nazwą funkcji a nawiasem otwierającym, ponieważ są one ze sobą ściśle związane.
- Wcięcia (Indentation): Są kluczowe dla wizualizacji hierarchii kodu. Pozwalają programiście szybko przeskakiwać fragmenty zakresów (np. instrukcje
iflubwhile), które go w danej chwili nie interesują. - Brak wyrównywania w kolumnach: Nie należy wyrównywać nazw zmiennych ani wartości przypisań w kolumnach. Taki zabieg odciąga wzrok od istotnych informacji i jest zazwyczaj usuwany przez automatyczne formattery kodu.
Przykłady kodu
Gęstość pionowa i zbędne komentarze
Poniżej przykład, jak zbędne komentarze psują relację między zmiennymi (źle):
public class ReporterConfig {
/**
* Nazwa klasy nasłuchu raportu.
*/
private String m_className;
/**
* Właściwości nasłuchu raportu.
*/
private List<Property> m_properties = new ArrayList<Property>();
public void addProperty(Property property) {
m_properties.add(property);
}
}
Po usunięciu szumu i zachowaniu gęstości pionowej (dobrze):
public class ReporterConfig {
private String m_className;
private List<Property> m_properties = new ArrayList<Property>();
public void addProperty(Property property) {
m_properties.add(property);
}
}
Dzięki takiemu formatowaniu klasę można objąć wzrokiem bez przemieszczania go po kodzie w poszukiwaniu sensu.
Zależności funkcji w pionie
Funkcja wywołująca powinna znajdować się nad wywoływaną (dobrze):
public Response makeResponse(FitNesseContext context, Request request) throws Exception {
String pageName = getPageNameOrDefault(request, "FrontPage");
loadPage(pageName, context);
if (page == null)
return notFoundResponse(context, request);
else
return makePageResponse(context);
}
private String getPageNameOrDefault(Request request, String defaultPageName) {
String pageName = request.getResource();
if (StringUtil.isBlank(pageName))
pageName = defaultPageName;
return pageName;
}
Taki układ tworzy naturalny przepływ programu od ogółu do szczegółu.
Wyrównywanie poziome (czego nie robić)
Wyrównywanie w kolumnach jest nieużyteczne i maskuje problemy projektowe (źle):
public class FitNesseExpediter implements ResponseSender {
private Socket socket;
private InputStream input;
private OutputStream output;
private Request request;
private Response response;
private FitNesseContext context;
}
Zamiast tego stosuj naturalne odstępy (dobrze):
public class FitNesseExpediter implements ResponseSender {
private Socket socket;
private InputStream input;
private OutputStream output;
private Request request;
private Response response;
private FitNesseContext context;
}
Jeśli lista zmiennych jest tak długa, że kusi jej wyrównanie, to problemem jest zbyt duża klasa, a nie brak wyrównania.
Łamanie wcięć
Nawet w krótkich instrukcjach nie należy „upychać” kodu w jednym wierszu (źle):
public class CommentWidget extends TextWidget {
public CommentWidget(ParentWidget parent, String text) {super(parent, text);}
public String render() throws Exception {return ""; }
}
Lepiej zachować pełną strukturę wcięć (dobrze):
public class CommentWidget extends TextWidget {
public CommentWidget(ParentWidget parent, String text) {
super(parent, text);
}
public String render() throws Exception {
return "";
}
}
Praktyczne wnioski
- Przestań traktować formatowanie jako sprawę drugorzędną. Kod, który „tylko działa”, ale jest nieczytelny, jest nieprofesjonalny i generuje dług techniczny.
- Ustal z zespołem jeden wspólny standard. Nie ma znaczenia, gdzie stawiacie klamry, dopóki wszyscy robią to identycznie. Spójność stylu w projekcie buduje zaufanie czytelnika.
- Skonfiguruj formatter w IDE. Wykorzystaj narzędzia automatyczne, aby wymuszały ustalone zasady zespołowe przy każdym zapisie pliku.
- Stosuj zasadę bliskości. Deklaruj zmienne tam, gdzie ich używasz, a nie na początku każdej funkcji „na zapas”.
- Dbaj o hierarchię pionową. Pisz kod tak, aby czytało się go od góry do dołu. Jeśli musisz skakać między początkiem a końcem pliku, by zrozumieć prosty algorytm, to formatowanie pionowe jest do poprawy.
- Usuwaj martwy kod i zbędny szum. Puste konstruktory, nieużywane zmienne i komentarze typu „zwiększenie i” zaciemniają obraz i utrudniają komunikację.