Sześć przykazań dobrego kodu: pisz kod, który przetrwa próbę czasu
Opublikowany: 2022-03-11Ludzie zmagają się ze sztuką i nauką programowania komputerów dopiero od około pół wieku. W porównaniu z większością sztuk i nauk, informatyka jest pod wieloma względami wciąż tylko małym dzieckiem, chodzącym po ścianach, potykającym się o własne stopy i od czasu do czasu rzucającym jedzeniem po stole.
Jako konsekwencja jego względnej młodości, nie sądzę, abyśmy osiągnęli konsensus co do tego, jaka jest właściwa definicja „dobrego kodu”, ponieważ ta definicja wciąż ewoluuje. Niektórzy powiedzą, że „dobry kod” to kod ze 100% pokryciem testów. Inni powiedzą, że jest superszybki, ma zabójczą wydajność i będzie działał akceptowalnie na 10-letnim sprzęcie.
Chociaż są to cele godne pochwały dla programistów, ośmielam się jednak dorzucić inny cel: łatwość utrzymania. W szczególności „dobry kod” to kod, który jest łatwy i łatwy do utrzymania przez organizację (nie tylko przez jej autora!) i będzie działał dłużej niż tylko sprint, w którym został napisany. Oto kilka rzeczy, które odkryłem w moim kariera inżyniera w dużych i małych firmach w USA i za granicą, które wydają się korelować z łatwym w utrzymaniu, „dobrym” oprogramowaniem.
Przykazanie nr 1: Traktuj swój kod tak, jak chcesz, aby kod innych traktował ciebie
Nie jestem pierwszą osobą, która napisze, że głównym odbiorcą twojego kodu nie jest kompilator/komputer, ale ktokolwiek następny musi czytać, rozumieć, utrzymywać i ulepszać kod (którym niekoniecznie będziesz ty za sześć miesięcy od teraz ). Każdy inżynier wart swojej zapłaty może stworzyć kod, który „działa”; Tym, co wyróżnia doskonałego inżyniera, jest to, że potrafią efektywnie pisać kod, który można konserwować, który wspiera biznes w dłuższej perspektywie i potrafią rozwiązywać problemy w prosty i przejrzysty i łatwy w utrzymaniu sposób.
W każdym języku programowania można napisać dobry lub zły kod. Zakładając, że oceniamy język programowania na podstawie tego, jak dobrze ułatwia pisanie dobrego kodu (w każdym razie powinien to być przynajmniej jeden z najważniejszych kryteriów), każdy język programowania może być „dobry” lub „zły” w zależności od tego, jak jest używany (lub nadużywany). ).
Przykładem języka, który przez wielu uważany jest za „czysty” i czytelny, jest Python. Sam język wymusza pewien poziom dyscypliny białej przestrzeni, a wbudowane interfejsy API są obfite i dość spójne. To powiedziawszy, możliwe jest tworzenie niewypowiedzianych potworów. Na przykład, można zdefiniować klasę i zdefiniować/przedefiniować/oddefiniować dowolną i każdą metodę w tej klasie w czasie wykonywania (często określane jako łatanie małp). Ta technika naturalnie prowadzi w najlepszym przypadku do niespójnego interfejsu API, aw najgorszym do niemożliwego do debugowania potwora. Można by naiwnie pomyśleć „oczywiście, ale nikt tego nie robi!” Niestety tak i nie trzeba długo przeglądać pypi, zanim natkniesz się na pokaźne (i popularne!) biblioteki, które (nad)używają łatania małp w dużym stopniu jako rdzenia ich interfejsów API. Niedawno użyłem biblioteki sieciowej, której całe API zmienia się w zależności od stanu sieci obiektu. Wyobraź sobie na przykład wywołanie client.connect() i czasami otrzymywanie błędu MethodDoesNotExist zamiast HostNotFound lub NetworkUnavailable .
Przykazanie nr 2: Dobry kod jest łatwy do odczytania i zrozumienia, zarówno w części, jak i w całości
Dobry kod jest łatwo czytany i rozumiany, w części i w całości, przez innych (a także przez autora w przyszłości, starającego się uniknąć syndromu „Czy naprawdę to napisałem?” ).
Przez „w części” rozumiem, że jeśli otworzę jakiś moduł lub funkcję w kodzie, powinienem być w stanie zrozumieć, co to robi, bez konieczności czytania całej reszty kodu. Powinno być jak najbardziej intuicyjne i samodokumentujące.
Kod, który stale odwołuje się do drobnych szczegółów, które wpływają na zachowanie z innych (pozornie nieistotnych) części bazy kodu, jest jak czytanie książki, w której musisz odwołać się do przypisów lub załącznika na końcu każdego zdania. Nigdy nie przebrniesz przez pierwszą stronę!
Kilka innych przemyśleń na temat czytelności „lokalnej”:
Dobrze zamknięty kod wydaje się być bardziej czytelny, oddzielając obawy na każdym poziomie.
Nazwy mają znaczenie. Aktywuj Myślenie Fast and Slow'ssystem 2 sposób, w którym mózg formułuje myśli i umieszcza pewne rzeczywiste, staranne przemyślenia w nazwach zmiennych i metod. Kilka dodatkowych sekund może przynieść znaczne korzyści. Dobrze nazwana zmienna może sprawić, że kod będzie znacznie bardziej intuicyjny, podczas gdy zmienna o złej nazwie może prowadzić do fałszerstw i zamieszania.
Spryt jest wrogiem. Używając wymyślnych technik, paradygmatów lub operacji (takich jak listy złożone lub operatory trójskładnikowe), należy używać ich w taki sposób, aby kod był bardziej czytelny, a nie tylko krótszy.
Konsekwencja to dobra rzecz. Spójność stylu, zarówno pod względem sposobu zakładania szelek, jak i operacji, znacznie poprawia czytelność.
Separacja obaw. Dany projekt zarządza niezliczoną liczbą lokalnie ważnych założeń w różnych punktach bazy kodu. Narażaj każdą część kodu na jak najmniejszą liczbę z tych problemów, jak to tylko możliwe. Załóżmy, że masz system zarządzania ludźmi, w którym obiekt osoby może czasami mieć puste nazwisko. Dla kogoś piszącego kod na stronie, która wyświetla obiekty osób, może to być naprawdę niezręczne! I jeśli nie prowadzisz podręcznika „Niezręczne i nieoczywiste założenia, które ma nasza baza kodów” (wiem, że nie wiem), programista strony displayowej nie będzie wiedział, że nazwiska mogą być puste i prawdopodobnie napisze kod ze wskaźnikiem zerowym wyjątek w nim, gdy pojawia się ostatnia wielkość liter oznaczająca nazwisko. Zamiast tego zajmij się tymi przypadkami za pomocą dobrze przemyślanych interfejsów API i kontraktów, których różne elementy Twojej bazy kodu używają do interakcji ze sobą.
Przykazanie 3: Dobry kod ma dobrze przemyślany układ i architekturę, dzięki którym zarządzanie stanem staje się oczywiste
Państwo jest wrogiem. Czemu? Ponieważ jest to najbardziej złożona część każdej aplikacji i należy się nią zająć bardzo celowo i przemyślanie. Typowe problemy obejmują niespójności bazy danych, częściowe aktualizacje interfejsu użytkownika, w których nowe dane nie są wszędzie odzwierciedlane, operacje poza kolejnością lub po prostu niesłusznie skomplikowany kod z instrukcjami if i gałęziami, które są wszędzie, co prowadzi do trudnego do odczytania, a nawet trudniejszego w utrzymaniu kodu. Umieszczenie stanu na piedestale, aby był traktowany z wielką ostrożnością, oraz bycie niezwykle konsekwentnym i przemyślanym w odniesieniu do sposobu uzyskiwania dostępu do stanu i jego modyfikacji, znacznie upraszcza bazę kodu. Niektóre języki (na przykład Haskell) wymuszają to na poziomie programistycznym i składniowym. Zdziwiłbyś się, jak bardzo przejrzystość bazy kodu może się poprawić, jeśli masz biblioteki czystych funkcji, które nie mają dostępu do żadnego stanu zewnętrznego, a następnie mały obszar kodu stanowego, który odwołuje się do czystej funkcjonalności zewnętrznej.

Przykazanie nr 4: Dobry kod nie wynajduje koła na nowo, stoi na barkach gigantów
Zanim wymyślisz koło na nowo, zastanów się, jak powszechny jest problem, który próbujesz rozwiązać, lub jaką funkcję chcesz spełnić. Ktoś mógł już wdrożyć rozwiązanie, które możesz wykorzystać. Poświęć trochę czasu na przemyślenie i zbadanie wszelkich takich opcji, jeśli są odpowiednie i dostępne.
To powiedziawszy, całkowicie rozsądnym kontrargumentem jest to, że zależności nie są „za darmo” bez żadnych wad. Korzystając z biblioteki innej firmy lub biblioteki open source, która dodaje kilka interesujących funkcji, zobowiązujesz się do tej biblioteki i stajesz się od niej zależny. To duże zobowiązanie; jeśli jest to ogromna biblioteka i potrzebujesz tylko niewielkiej ilości funkcjonalności, czy naprawdę chcesz mieć ciężar aktualizacji całej biblioteki, jeśli uaktualnisz, na przykład, do Pythona 3.x? Co więcej, jeśli napotkasz błąd lub chcesz ulepszyć funkcjonalność, jesteś albo zależny od autora (lub dostawcy), który dostarczy poprawkę lub ulepszenie, albo, jeśli jest to open source, znajdziesz się w sytuacji, w której możesz zbadać ( potencjalnie znaczną) bazę kodu, której zupełnie nie znasz, próbując naprawić lub zmodyfikować niejasną część funkcjonalności.
Z pewnością im lepiej wykorzystany jest kod, od którego jesteś zależny, tym mniejsze prawdopodobieństwo, że będziesz musiał sam zainwestować czas w konserwację. Najważniejsze jest to, że warto przeprowadzić własne badania i dokonać własnej oceny, czy włączyć zewnętrzną technologię i ile konserwacji ta konkretna technologia doda do twojego stosu.
Poniżej znajdują się niektóre z bardziej powszechnych przykładów rzeczy, których prawdopodobnie nie powinieneś wymyślać na nowo w swoim projekcie w epoce nowożytnej (chyba że SĄ to twoje projekty).
Bazy danych
Dowiedz się, którego CAP potrzebujesz do swojego projektu, a następnie wybierz bazę danych o odpowiednich właściwościach. Baza danych nie oznacza już tylko MySQL, możesz wybrać spośród:
- „Tradycyjny” schemat SQL: Postgres / MySQL / MariaDB / MemSQL / Amazon RDS itp.
- Magazyny wartości kluczowych: Redis / Memcache / Riak
- NoSQL: MongoDB/Cassandra
- Hostowane bazy danych: AWS RDS / DynamoDB / AppEngine Datastore
- Podnoszenie ciężarów: Amazon MR / Hadoop (Hive/Pig) / Cloudera / Google Big Query
- Szalone rzeczy: Mnesia Erlanga, podstawowe dane iOS
Warstwy abstrakcji danych
W większości przypadków nie powinieneś pisać nieprzetworzonych zapytań do dowolnej bazy danych, której używasz. Najprawdopodobniej istnieje biblioteka, która znajduje się pomiędzy bazą danych a kodem aplikacji, oddzielając problemy związane z zarządzaniem współbieżnymi sesjami bazy danych i szczegółami schematu od głównego kodu. Przynajmniej nigdy nie powinieneś umieszczać surowych zapytań ani SQL w środku kodu aplikacji. Zamiast tego zapakuj go w funkcję i scentralizuj wszystkie funkcje w pliku o nazwie coś naprawdę oczywistego (np. „queries.py”). Na przykład wiersz taki jak users = load_users() , jest nieskończenie łatwiejszy do odczytania niż users = db.query(“SELECT username, foo, bar from users LIMIT 10 ORDER BY ID”) . Ten rodzaj centralizacji znacznie ułatwia również zachowanie spójnego stylu w zapytaniach i ogranicza liczbę miejsc, w których można przejść do zmiany zapytań w przypadku zmiany schematu.
Inne popularne biblioteki i narzędzia do rozważenia wykorzystania dźwigni
- Usługi kolejkowania lub Pub/Sub. Wybierz dostawców AMQP, ZeroMQ, RabbitMQ, Amazon SQS
- Przechowywanie. Amazon S3, Google Cloud Storage
- Monitorowanie: Grafit/Grafit hostowany, AWS Cloud Watch, New Relic
- Zbieranie / agregacja dzienników. Logly, Splunk
Automatyczne skalowanie
- Automatyczne skalowanie. Heroku, AWS Beanstalk, AppEngine, AWS Opsworks, Ocean cyfrowy
Przykazanie 5: Nie przekraczaj strumieni!
Istnieje wiele dobrych modeli do projektowania programowania, pub/sub, aktorzy, MVC itp. Wybierz ten, który Ci się najbardziej podoba i trzymaj się go. Różne rodzaje logiki zajmujące się różnymi rodzajami danych powinny być fizycznie wyizolowane w bazie kodu (znowu to oddzielenie koncepcji obaw i zmniejszenie obciążenia poznawczego przyszłego czytelnika). Kod, który aktualizuje Twój interfejs użytkownika, powinien fizycznie różnić się od kodu, który oblicza, na przykład, co trafia do interfejsu użytkownika.
Przykazanie #6: Kiedy to możliwe, pozwól komputerowi wykonać pracę
Jeśli kompilator potrafi wychwycić błędy logiczne w twoim kodzie i zapobiec złemu zachowaniu, błędom lub całkowitym awariom, bezwzględnie powinniśmy to wykorzystać. Oczywiście niektóre języki mają kompilatory, które to ułatwiają niż inne. Haskell, na przykład, ma słynny, ścisły kompilator, który powoduje, że programiści spędzają większość swojego wysiłku tylko na skompilowaniu kodu. Jednak po skompilowaniu „po prostu działa”. Dla tych z was, którzy nigdy nie pisali w mocno zapisanym języku funkcjonalnym, może się to wydawać śmieszne lub niemożliwe, ale nie wierzcie mi na słowo. Poważnie, kliknij niektóre z tych linków, absolutnie możliwe jest życie w świecie bez błędów w czasie wykonywania. I to naprawdę jest takie magiczne.
Trzeba przyznać, że nie każdy język ma kompilator lub składnię, które nadają się do wielu (lub w niektórych przypadkach nawet!) sprawdzania w czasie kompilacji. Dla tych, którzy tego nie robią, poświęć kilka minut na zbadanie, jakie opcjonalne kontrole rygorystyczności możesz włączyć w swoim projekcie i oceń, czy mają one dla Ciebie sens. Krótka, nie wyczerpująca lista niektórych popularnych, z których ostatnio korzystałem w przypadku języków z łagodnymi środowiskami uruchomieniowymi, obejmuje:
- Python: pylint, pyflakes, ostrzeżenia, ostrzeżenia w emacs
- Rubin: ostrzeżenia
- JavaScript: jslint
Wniosek
W żadnym wypadku nie jest to wyczerpująca ani idealna lista przykazań do tworzenia „dobrego” (tj. łatwego w utrzymaniu) kodu. To powiedziawszy, jeśli każda baza kodu, którą kiedykolwiek musiałem zdobyć w przyszłości, byłaby zgodna z nawet połową koncepcji z tej listy, będę miał o wiele mniej siwych włosów i być może nawet będę w stanie dodać dodatkowe pięć lat pod koniec mojego życia. I na pewno praca będzie dla mnie przyjemniejsza i mniej stresująca.
