Co każdy programista wiedzieć powinien



Działaj z rozwagą

Cokolwiek podejmiesz, działaj z rozwagą i rozważ konsekwencje.- Anonim

Bez względu na to, jak wygodny wygląda harmonogram na początku iteracji, nie możesz uniknąć presji przez pewien czas. Jeśli okaże się, że musisz wybierać między "zrobić to dobrze" a "zrobić to szybko", często atrakcyjne jest "zrób to szybko" ze zrozumieniem, że wrócisz i naprawisz to później. Kiedy składasz tę obietnicę sobie, swojemu zespołowi i klientowi, masz to na myśli. Ale zbyt często następna iteracja przynosi nowe problemy i skupiasz się na nich. Ten rodzaj odroczonej pracy jest znany jako dług techniczny i nie jest twoim przyjacielem. W szczególności Martin Fowler nazywa ten celowy dług techniczny w swojej taksonomii długu technicznego i nie należy go mylić z nieumyślnym długiem technicznym. Dług techniczny jest jak pożyczka: czerpiesz z niego korzyści w krótkim okresie, ale musisz płacić od niego odsetki, dopóki nie zostanie w pełni spłacony. Skróty w kodzie utrudniają dodawanie funkcji lub refaktoryzację kodu. Są pożywką dla wad i kruchych przypadków testowych. Im dłużej go zostawisz, tym gorzej. Zanim zajmiesz się oryginalną poprawką, może istnieć cały stos nie do końca właściwych wyborów projektowych nałożonych na oryginalny problem, co znacznie utrudnia refaktoryzację i poprawianie kodu. W rzeczywistości często tylko wtedy, gdy sprawy mają się tak źle, że musisz naprawić pierwotny problem, naprawdę wracasz, aby go naprawić. A do tego czasu często jest tak trudno naprawić, że naprawdę nie możesz sobie pozwolić na czas ani ryzyko. Bywają sytuacje, w których musisz zaciągnąć dług techniczny, aby dotrzymać terminu lub zaimplementować cienką część funkcji. Staraj się nie być w tej sytuacji, ale jeśli sytuacja absolutnie tego wymaga, śmiało. Ale (i to jest duże ale) musisz śledzić dług techniczny i szybko go spłacać, w przeciwnym razie sprawy szybko się pogorszą. Gdy tylko podejmiesz decyzję o kompromisie, napisz kartę zadań lub zarejestruj ją w swoim systemie śledzenia problemów, aby mieć pewność, że nie zostanie zapomniana. Jeśli zaplanujesz spłatę długu w kolejnej iteracji, koszt będzie minimalny. Pozostawienie niespłaconego długu spowoduje naliczanie odsetek, które należy śledzić, aby koszty były widoczne. Podkreśli to wpływ zadłużenia technicznego projektu na biznesową wartość i umożliwi odpowiednią priorytetyzację spłaty. Wybór sposobu obliczania i śledzenia zainteresowania będzie zależeć od konkretnego projektu, ale musisz go śledzić. Spłać jak najszybciej dług techniczny. Byłoby nieroztropnie postąpić inaczej.

Zastosuj zasady funkcjonalne

Programowanie funkcjonalne cieszy się ostatnio odnowionym zainteresowaniem społeczności programistów głównego nurtu. Częściowo jest to spowodowane tym, że pojawiające się właściwości paradygmatu funkcjonalnego są dobrze przygotowane do sprostania wyzwaniom związanym z przejściem w naszej branży w kierunku wielordzeniowości. Jednak, chociaż jest to z pewnością ważna aplikacja, nie jest to powód, dla którego ten artykuł upomina cię, abyś znał swoje programowanie funkcjonalne. Opanowanie paradygmatu programowania funkcyjnego może znacznie poprawić jakość kodu, który piszesz w innych kontekstach. Jeśli dogłębnie zrozumiesz i zastosujesz paradygmat funkcjonalny, Twoje projekty będą wykazywać znacznie wyższy stopień przejrzystości referencyjnej. Przejrzystość referencyjna jest bardzo pożądaną właściwością: oznacza, że funkcje konsekwentnie dają te same wyniki przy tych samych danych wejściowych, niezależnie od tego, gdzie i kiedy są wywoływane. Oznacza to, że ocena funkcji zależy mniej - najlepiej, wcale nie od skutków ubocznych stanu mutowalnego. Główną przyczyną błędów w kodzie imperatywnym są zmienne zmienne. Każdy, kto to czyta, zbada, dlaczego niektóre wartości nie są zgodne z oczekiwaniami w określonej sytuacji. Semantyka widoczności może pomóc złagodzić te podstępne defekty lub przynajmniej drastycznie zawęzić ich lokalizację, ale ich prawdziwym winowajcą może być w rzeczywistości opatrzność projektów wykorzystujących nadmierną zmienność. I z pewnością nie otrzymujemy w tym zakresie dużej pomocy od branży. Wprowadzenia do orientacji obiektowej milcząco promują takie projektowanie, ponieważ często pokazują przykłady złożone z grafów stosunkowo długowiecznych obiektów, które radośnie wywołują na sobie metody mutatorów, co może być niebezpieczne. Jednak dzięki wnikliwemu projektowi opartemu na testach, szczególnie gdy upewnisz się, że "naśladujesz role, a nie obiekty", niepotrzebną zmienność można zaprojektować. Wynik netto to projekt, który zazwyczaj ma lepszą alokację odpowiedzialności z liczniejszymi, mniejszymi funkcjami, które działają na argumentach przekazanych do nich, zamiast odwoływać się do zmiennych składowych. Będzie mniej defektów, a ponadto będą one często prostsze do debugowania, ponieważ łatwiej jest zlokalizować, gdzie w tych projektach wprowadzana jest nieuczciwa wartość, niż w inny sposób wywnioskować konkretny kontekst, który skutkuje błędnym przypisaniem. To przekłada się na znacznie wyższy stopień przejrzystości referencyjnej i na pewno nic nie wbije tych pomysłów tak głęboko w twoje kości, jak nauka funkcjonalnego języka programowania, w którym ten model obliczeń jest normą. Oczywiście takie podejście nie jest optymalne we wszystkich sytuacjach. Na przykład w systemach zorientowanych obiektowo ten styl często daje lepsze wyniki przy opracowywaniu modelu domeny (tj. tam, gdzie współpraca służy do przełamania złożoności reguł biznesowych) niż przy opracowywaniu interfejsu użytkownika. Opanuj paradygmat programowania funkcjonalnego, aby móc rozsądnie zastosować wyciągnięte wnioski w innych dziedzinach. Twoje systemy obiektów (na przykład) będą rezonować z referencyjną dobrocią przezroczystości i będą znacznie bliższe swoim funkcjonalnym odpowiednikom, niż wielu by ci uwierzyło. W rzeczywistości niektórzy twierdzą nawet, że na swoim szczycie programowanie funkcjonalne i orientacja obiektowa są jedynie wzajemnym odbiciem, formą obliczeniowego yin i yang.

Zapytaj: "Co zrobiłby użytkownik?" (Nie jesteś użytkownikiem)

Wszyscy mamy skłonność do zakładania, że inni ludzie myślą tak jak my. Ale tak nie jest. Psychologowie nazywają to fałszywym uprzedzeniem konsensusu. Kiedy ludzie myślą lub działają inaczej niż my, jest całkiem prawdopodobne, że w jakiś sposób oznaczymy ich (podświadomie) jako wadliwych. To uprzedzenie wyjaśnia, dlaczego programistom tak trudno jest postawić się w sytuacji użytkowników. Użytkownicy nie myślą jak programiści. Po pierwsze, spędzają znacznie mniej czasu na komputerach. Nie wiedzą ani nie dbają o to, jak działa komputer. Oznacza to, że nie mogą korzystać z żadnej z baterii technik rozwiązywania problemów, które są tak dobrze znane programistom. Nie rozpoznają wzorców i wskazówek używanych przez programistów do pracy z interfejsem, za jego pośrednictwem i wokół niego. Najlepszym sposobem, aby dowiedzieć się, jak myśli użytkownik, jest jego obejrzenie. Poproś użytkownika o wykonanie zadania przy użyciu oprogramowania podobnego do tego, które tworzysz. Upewnij się, że zadanie jest prawdziwe: "Dodaj kolumnę liczb" jest OK; "Oblicz swoje wydatki za ostatni miesiąc" jest lepsze. Unikaj zadań, które są zbyt szczegółowe, takie jak "Czy możesz wybrać te komórki arkusza kalkulacyjnego i wprowadzić poniżej formułę SUMA?" - w tym pytaniu jest duża wskazówka. Poproś użytkownika, aby omówił swoje postępy. Nie przerywaj. Nie próbuj pomagać. Zadaj sobie pytanie: "Dlaczego on to robi?" i "Dlaczego ona tego nie robi?" Pierwszą rzeczą, którą zauważysz, jest to, że użytkownicy robią wiele rzeczy w podobny sposób. Starają się wykonywać zadania w tej samej kolejności - i popełniają te same błędy w tym samym miejsca. Powinieneś projektować wokół tego podstawowego zachowania. Różni się to od spotkań projektowych, na których ludzie mają tendencję do słuchania, gdy ktoś mówi: "A co, jeśli użytkownik chce…?" Prowadzi to do rozbudowanych funkcji i zamieszania co do tego, czego chcą użytkownicy. Oglądanie użytkowników eliminuje to zamieszanie. Zobaczysz, że użytkownicy utkną. Kiedy utkniesz, rozglądasz się. Gdy użytkownicy utkną, zawężają swoją uwagę. Trudniej jest im zobaczyć rozwiązania w innym miejscu na ekranie. To jeden z powodów, dla których tekst pomocy jest słabym rozwiązaniem problemu złego projektu interfejsu użytkownika. Jeśli potrzebujesz instrukcji lub tekstu pomocy, zlokalizuj go tuż obok problematycznych obszarów. Wąski zakres uwagi użytkownika sprawia, że podpowiedzi są bardziej przydatne niż menu pomocy. Użytkownicy mają tendencję do przedzierania się. Znajdą sposób, który zadziała i będą się go trzymać, bez względu na to, jak zawiłe. Lepiej podać jeden naprawdę oczywisty sposób robienia rzeczy niż dwa lub trzy skróty. Przekonasz się również, że istnieje przepaść między tym, czego chcą użytkownicy, a tym, co faktycznie robią. To niepokojące, ponieważ normalnym sposobem zbierania wymagań użytkowników jest ich zapytanie. Dlatego najlepszym sposobem na uchwycenie wymagań jest obserwowanie użytkowników. Spędzenie godziny na oglądaniu użytkowników jest bardziej pouczające niż spędzenie dnia na zgadywaniu, czego chcą.

Zautomatyzuj swój standard kodowania

Prawdopodobnie też tam byłeś. Na początku projektu każdy ma wiele dobrych intencji - nazwijmy je "postanowieniami nowego projektu". Dość często wiele z tych rezolucji jest spisywanych w dokumentach. Te o kodzie trafiają do standardu kodowania projektu. Podczas spotkania inauguracyjnego główny programista przegląda dokument i, w najlepszym przypadku, wszyscy zgadzają się, że spróbują postępować zgodnie z nimi. Jednak po rozpoczęciu projektu te dobre intencje są porzucane, pojedynczo. Kiedy projekt jest w końcu dostarczany, kod wygląda jak bałagan i wydaje się, że nikt nie wie, jak do tego doszło. Kiedy coś poszło nie tak? Prawdopodobnie już na spotkaniu inauguracyjnym. Niektórzy członkowie projektu nie zwracali na to uwagi. Inni nie rozumieli, o co chodzi. Co gorsza, niektórzy się nie zgodzili i już planowali swój standardowy bunt kodowania. W końcu niektórzy zrozumieli i zgodzili się, ale kiedy presja w projekcie była zbyt duża, musieli coś odpuścić. Dobrze sformatowany kod nie zapewnia punktów u klienta, który chce większej funkcjonalności. Co więcej, przestrzeganie standardu kodowania może być dość nudnym zadaniem, jeśli nie jest zautomatyzowane. Po prostu spróbuj ręcznie wciąć niechlujną klasę, aby się przekonać. Ale jeśli to jest taki problem, dlaczego chcemy standardu kodowania w pierwszym miejsce? Jednym z powodów formatowania kodu w jednolity sposób jest to, że nikt nie może "posiadać" fragmentu kodu tylko poprzez formatowanie go na swój prywatny sposób. Możemy chcieć uniemożliwić programistom korzystanie z niektórych antywzorców, aby uniknąć niektórych typowych błędów. Podsumowując, standard kodowania powinien ułatwiać pracę w projekcie i utrzymywać tempo rozwoju od początku do końca. Wynika z tego, że wszyscy powinni również zgodzić się na standard kodowania - nie pomaga, jeśli jeden programista używa trzech spacji do wcięcia kodu, a inny używa czterech. Istnieje wiele narzędzi, które można wykorzystać do tworzenia raportów dotyczących jakości kodu oraz dokumentowania i utrzymywania standardu kodowania, ale to nie jest całe rozwiązanie. Powinna być zautomatyzowana i egzekwowana tam, gdzie to możliwe. Oto kilka przykładów:

•  Upewnij się, że formatowanie kodu jest częścią procesu kompilacji, tak aby wszyscy uruchamiali je automatycznie za każdym razem, gdy kompilują kod.
•  Użyj narzędzi do statycznej analizy kodu, aby przeskanować kod w poszukiwaniu niechcianych antywzorców. Jeśli jakieś zostaną znalezione, przerwij kompilację.
•  Naucz się konfigurować te narzędzia, aby móc skanować w poszukiwaniu własnych, specyficznych dla projektu antywzorców.
•  Nie tylko mierz pokrycie testowe, ale także automatycznie sprawdzaj wyniki. Ponownie przerwij kompilację, jeśli pokrycie testowe jest zbyt niskie.
Spróbuj zrobić to dla wszystkiego, co uważasz za ważne. Nie będziesz w stanie zautomatyzować wszystkiego, na czym naprawdę Ci zależy. Jeśli chodzi o rzeczy, których nie możesz automatycznie oznaczyć lub naprawić, potraktuj je jako zestaw wytycznych uzupełniających standard kodowania, który jest zautomatyzowany, ale zaakceptuj, że Ty i Twoi współpracownicy możecie nie przestrzegać ich tak pilnie. Wreszcie standard kodowania powinien być dynamiczny, a nie statyczny. Wraz z rozwojem projektu zmieniają się jego potrzeby, a to, co mogło wydawać się inteligentne na początku, niekoniecznie musi być mądre kilka miesięcy później.

Piękno tkwi w prostocie

Jest jeden cytat z Platona, który moim zdaniem jest szczególnie dobry dla wszystkich programistów, aby znać i trzymać się blisko ich serc:

Piękno stylu i harmonia, wdzięk i dobry rytm zależą od prostoty.

W jednym zdaniu podsumowuje to wartości, do których my, programiści, powinniśmy dążyć. W naszym kodzie dążymy do kilku rzeczy:

•  Czytelność
•  Łatwość utrzymania
•  Szybkość rozwoju
•  Nieuchwytna jakość piękna

Platon mówi nam, że czynnikiem umożliwiającym wszystkie te cechy jest prostota. Czym jest piękny kod? To potencjalnie bardzo subiektywne pytanie. Postrzeganie piękna w dużej mierze zależy od indywidualnego pochodzenia, tak jak wiele z naszego postrzegania czegokolwiek zależy od naszego pochodzenia. Osoby z wykształceniem artystycznym inaczej postrzegają (a przynajmniej podchodzą do) piękno niż osoby wykształcone w naukach ścisłych. Kierunki artystyczne mają tendencję do podejścia do piękna w oprogramowaniu, porównując oprogramowanie do dzieł sztuki, podczas gdy kierunki ścisłe mają tendencję do mówienia o symetrii i złotym podziale, próbując zredukować rzeczy do formuł. Z mojego doświadczenia wynika, że prostota jest podstawą większości argumentów obu stron. Pomyśl o kodzie źródłowym, który przestudiowałeś. Jeśli nie spędziłeś czasu na studiowaniu kodu innych ludzi, przestań to czytać w tej chwili i znajdź jakiś kod open source do przestudiowania. Na serio! Mam to na myśli! Poszukaj w Internecie kodu w wybranym przez Ciebie języku, napisanego przez znanego, uznanego eksperta. Jesteś z powrotem? Dobrze. Gdzie byliśmy? Ach, tak… Odkryłem, że kod, który współbrzmi ze mną i który uważam za piękny, ma wiele cech wspólnych. Najważniejszym z nich jest prostota. Uważam, że bez względu na to, jak złożona jest cała aplikacja lub system, poszczególne części muszą być proste: proste obiekty z jedną odpowiedzialnością zawierające podobnie proste, ukierunkowane metody z opisowymi nazwami. Niektórzy uważają, że pomysł posiadania krótkich metod składających się z 5-10 linijek kodu jest ekstremalny, a niektóre języki bardzo to utrudniają, ale myślę, że taka zwięzłość jest jednak pożądanym celem. Najważniejsze jest to, że piękny kod to prosty kod. Każda pojedyncza część jest prosta, z prostymi obowiązkami i prostymi relacjami z innymi częściami systemu. W ten sposób możemy utrzymać nasze systemy podatne na konserwację w czasie, z czystym, prostym i testowalnym kodem, zapewniając dużą szybkość rozwoju przez cały okres użytkowania systemu. Piękno rodzi się i znajduje w prostocie.

Zanim dokonasz restrukturyzacji

W pewnym momencie każdy programista będzie musiał dokonać resrukturyzacjii istniejącego kodu. Ale zanim to zrobisz, pomyśl o następujących rzeczach, ponieważ może to zaoszczędzić Tobie i innym dużo czasu (i bólu):

•  Najlepsze podejście do restrukturyzacji zaczyna się od podsumowania istniejącego kodu i testów napisanych na podstawie tego kodu. Pomoże Ci to zrozumieć mocne i słabe strony kodu w jego obecnej formie, dzięki czemu możesz zachować mocne strony, jednocześnie unikając błędów. Wszyscy myślimy, że możemy zrobić coś lepszego niż istniejący system… dopóki nie otrzymamy czegoś nie lepszego - lub nawet gorszego - niż poprzednie wcielenie, ponieważ nie nauczyliśmy się na błędach istniejącego systemu.

•  Unikaj pokusy pisania wszystkiego od nowa. Najlepiej jest ponownie wykorzystać jak najwięcej kodu. Bez względu na to, jak brzydki jest kod, został on już przetestowany, zrecenzowany itp. Wyrzucanie starego kodu - zwłaszcza jeśli był w produkcji - oznacza, że wyrzucasz miesiące (lub lata) przetestowanego, zahartowanego w boju kodu, który może mieć pewne obejścia i poprawki błędów, o których nie wiesz. Jeśli nie weźmiesz tego pod uwagę, nowy kod, który napiszesz, może skończyć się wyświetlaniem tych samych tajemniczych błędów, które zostały naprawione w starym kodzie. Zmarnuje to dużo czasu, wysiłku i wiedzy zdobytej przez lata.

•  Wiele stopniowych zmian jest lepszych niż jedna ogromna zmiana. Zmiany przyrostowe pozwalają łatwiej ocenić wpływ na system dzięki informacjom zwrotnym, takim jak testy. Nie jest zabawne widzieć setki niepowodzeń w testach po wprowadzeniu zmian. Może to prowadzić do frustracji i presji, które z kolei mogą prowadzić do złych decyzji. Łatwiej jest poradzić sobie z kilkoma niepowodzeniami testów na raz, co prowadzi do łatwiejszego podejścia.

•  Po każdej iteracji programistycznej ważne jest, aby upewnić się, że istniejące testy przeszły pomyślnie. Dodaj nowe testy, jeśli istniejące testy nie są wystarczające, aby uwzględnić wprowadzone zmiany. Nie wyrzucaj testów ze starego kodu bez należytego rozważenia. Na pierwszy rzut oka niektóre z tych testów mogą nie mieć zastosowania do twojego nowego projektu, ale warto byłoby zagłębić się w powody, dla których ten konkretny test został dodany.

•  Osobiste preferencje i ego nie powinny przeszkadzać. Jeśli coś nie jest zepsute, po co to naprawiać? To, że styl lub struktura kodu nie odpowiada Twoim osobistym preferencjom, nie jest ważnym powodem do restrukturyzacji. Myślenie, że mógłbyś wykonać lepszą pracę niż poprzedni programista, również nie jest uzasadnionym powodem.

•  Nowa technologia jest niewystarczającym powodem do refaktoryzacji. Jednym z najgorszych powodów do refaktoryzacji jest to, że obecny kod jest daleko w tyle za całą fajną technologią, którą mamy dzisiaj, i wierzymy, że nowy język lub framework może robić rzeczy o wiele bardziej elegancko. O ile analiza kosztów i korzyści nie wykaże, że nowy język lub framework przyniesie znaczną poprawę funkcjonalności, łatwości konserwacji lub produktywności, najlepiej pozostawić to bez zmian.

•  Pamiętaj, że ludzie popełniają błędy. Restrukturyzacja nie zawsze zagwarantuje, że nowy kod będzie lepszy, a nawet tak dobry, jak poprzednia próba. Widziałem i brałem udział w kilku nieudanych próbach restrukturyzacji. To nie było ładne, ale było ludzkie.

Uważaj na współudział

To był mój pierwszy projekt w firmie. Właśnie skończyłem studia i chciałem się wykazać, codziennie spóźniając się, przeglądając istniejący kod. Kiedy pracowałem nad moją pierwszą funkcją, bardzo uważałem, aby umieścić wszystko, czego się nauczyłem - komentowanie, logowanie, wyciąganie współdzielonego kodu do bibliotek tam, gdzie to możliwe, prace. Przegląd kodu, na który czułem się tak gotowy, przyszedł jako niegrzeczne przebudzenie - ponowne użycie było mile widziane! Jak to możliwe? Przez cały czas trwania studiów ponowne użycie było uważane za uosobienie wysokiej jakości inżynierii oprogramowania. Wszystkie artykuły, które przeczytałem, podręczniki, doświadczeni specjaliści od oprogramowania, którzy mnie nauczyli - czy to wszystko było złe? Okazuje się, że brakowało mi czegoś krytycznego. Kontekstu. Fakt, że dwie szalenie różne części systemu wykonały pewną logikę w ten sam sposób znaczyło mniej, niż myślałem. Dopóki nie wyciągnąłem tych bibliotek współdzielonego kodu, te części nie były od siebie zależne. Każdy mógł ewoluować niezależnie. Każdy może zmienić swoją logikę, aby dopasować ją do potrzeb zmieniającego się otoczenia biznesowego systemu. Te cztery linijki podobnego kodu były przypadkowe - anomalia czasowa, zbieg okoliczności. To znaczy, dopóki się nie pojawiłem. Biblioteki współdzielonego kodu, które stworzyłem, wiązały sznurowadła każdej stopy z drugą. Nie można było wykonać kroków według jednej domeny biznesowej bez uprzedniej synchronizacji z drugą. Koszty utrzymania tych niezależnych funkcji były kiedyś znikome, ale wspólna biblioteka wymagała o rząd wielkości więcej testów. Podczas gdy zmniejszyłem bezwzględną liczbę linii kodu w systemie, zwiększyłem liczbę zależności. Kontekstem tych zależności jest krytyczne - gdyby zostały zlokalizowane, udostępnienie mogło być uzasadnione i mieć pewną pozytywną wartość. Kiedy te zależności nie są utrzymywane w ryzach, ich wąsy oplatają większe problemy systemu, mimo że sam kod wygląda dobrze. Te błędy są podstępne, ponieważ w istocie brzmią jak dobry pomysł. Techniki te, zastosowane w odpowiednim kontekście, są cenne. W niewłaściwym kontekście zwiększają koszt, a nie wartość. Wchodząc do istniejącej bazy kodu bez wiedzy o tym, gdzie będą używane różne części, obecnie jestem znacznie bardziej ostrożny w kwestii tego, co jest udostępniane. Strzeż się akcji. Sprawdź swój kontekst. Dopiero wtedy kontynuuj.

Zasada skauta

Skauci mają zasadę: "Zawsze zostawiaj pole namiotowe czystsze, niż je znalazłeś". Jeśli znajdziesz bałagan na ziemi, posprzątasz go bez względu na to, kto mógł to zrobić. Celowo poprawiasz otoczenie dla kolejnej grupy obozowiczów. (Właściwie pierwotna forma tej zasady, napisana przez Roberta Stephensona Smytha Baden-Powella, ojca harcerstwa, brzmiała: "Spróbuj opuścić ten świat trochę lepszym, niż go znalazłeś"). Kod: "Zawsze sprawdzaj moduł w czystszym niż podczas sprawdzania"? Bez względu na to, kim był pierwotny autor, co jeśli zawsze staraliśmy się ulepszyć moduł, bez względu na to, jak mały? Jaki byłby wynik? Myślę, że gdybyśmy wszyscy postępowali zgodnie z tą prostą zasadą, zobaczylibyśmy koniec nieustannego pogarszania się naszych systemów oprogramowania. Zamiast tego nasze systemy stopniowo stawały się coraz lepsze w miarę ewolucji. Widzielibyśmy również zespoły dbające o system jako całość, a nie tylko osoby dbające o swoją małą część. Nie sądzę, aby ta zasada była zbyt duża, by prosić. Nie musisz dopracowywać każdego modułu przed zaewidencjonowaniem. Po prostu musisz uczynić go trochę lepszym niż wtedy, gdy go zaewidencjonowałeś. Oczywiście oznacza to, że każdy kod dodany do modułu musi być czysty. Oznacza to również, że przed ponownym zaewidencjonowaniem modułu wyczyścisz co najmniej jedną inną rzecz. Możesz po prostu poprawić nazwę jednej zmiennej lub podzielić jedną długą funkcję na dwie mniejsze. Możesz przerwać zależność cykliczną lub dodać interfejs, aby oddzielić zasady od szczegółów. Szczerze mówiąc, brzmi to dla mnie jak zwykła przyzwoitość - jak mycie rąk po skorzystaniu z toalety lub wyrzucanie śmieci do kosza zamiast upuszczania ich na podłogę. Rzeczywiście, pozostawienie bałaganu w kodeksie powinno być społecznie niedopuszczalne, podobnie jak zaśmiecanie. Powinno to być coś, czego się po prostu nie robi. Ale to coś więcej. Dbanie o własny kod to jedno. Dbanie o kod zespołu to zupełnie inna sprawa. Zespoły pomagają sobie nawzajem i sprzątają po sobie. Przestrzegają zasady Boy Scout, ponieważ jest to dobre dla wszystkich, a nie tylko dla nich samych.

Sprawdź swój kod, zanim zaczniesz obwiniać innych

Deweloperzy - wszyscy z nas ! - często mają problem z uwierzeniem, że nasz własny kod jest zepsuty. To po prostu tak nieprawdopodobne, że choć raz musi to być uszkodzony kompilator. Jednak w rzeczywistości bardzo (bardzo) niezwykłe jest to, że kod jest łamany przez błąd w kompilatorze, interpreterze, systemie operacyjnym, serwerze aplikacji, bazie danych, menedżerze pamięci lub jakimkolwiek innym oprogramowaniu systemowym. Tak, te błędy istnieją, ale są znacznie mniej powszechne, niż moglibyśmy sądzić. Kiedyś miałem prawdziwy problem z błędem kompilatora, który optymalizował zmienną pętli, ale wyobrażałem sobie, że mój kompilator lub system operacyjny ma błąd wiele razy. Zmarnowałem dużo czasu, czasu wsparcia i czasu zarządzania, tylko po to, by czuć się trochę głupio za każdym razem, gdy okazywało się to moim błędem. Zakładając, że narzędzia są powszechnie używane, dojrzałe i wykorzystywane w różnych stosach technologicznych, nie ma powodu, aby wątpić w jakość. Oczywiście, jeśli narzędzie jest wczesną wersją lub jest używane tylko przez kilka osób na całym świecie, lub jest rzadko pobieranym oprogramowaniem w wersji 0.1 o otwartym kodzie źródłowym, może być dobry powód, aby podejrzewać to oprogramowanie. (Podobnie, wersja alfa komercyjnego oprogramowania może być podejrzana.) Biorąc pod uwagę, jak rzadkie są błędy kompilatora, znacznie lepiej poświęcasz swój czas i energię na znalezienie błędu w swoim kodzie niż na udowodnienie, że kompilator jest zły. Obowiązują wszystkie zwykłe porady dotyczące debugowania, więc wyizoluj problem, stłum wywołania i otocz go testami; sprawdź konwencje wywoływania, biblioteki współdzielone i numery wersji; wyjaśnij to komuś innemu; zwracaj uwagę na uszkodzenie stosu i niezgodności typów zmiennych; i wypróbuj kod na różnych komputerach i różnych konfiguracjach kompilacji, takich jak debugowanie i wydanie. Kwestionuj własne założenia i założenia innych. Narzędzia od różnych dostawców mogą mieć wbudowane różne założenia - tak samo mogą być różne narzędzia od tego samego dostawcy. Kiedy ktoś inny zgłasza problem, którego nie możesz powielić, idź i zobacz, co robi. Mogą robić coś, o czym nigdy nie pomyślałeś lub robią coś w innej kolejności. Moją osobistą zasadą jest to, że jeśli mam błąd, którego nie mogę określić i zaczynam myśleć, że to kompilator, to czas poszukać uszkodzenia stosu. To jest szczególnie prawdziwe, jeśli dodanie kodu śledzenia powoduje, że problem się porusza. Problemy wielowątkowe to kolejne źródło błędów, które siwieją włosy i wywołują krzyki na maszynę. Wszystkie zalecenia dotyczące prostego kodu są mnożone, gdy system jest wielowątkowy. Nie można polegać na debugowaniu i testach jednostkowych w celu znalezienia takich błędów o jakiejkolwiek spójności, więc prostota projektu jest najważniejsza. Tak więc, zanim zaczniesz obwiniać kompilatora, pamiętaj o radzie Sherlocka Holmesa: "Kiedy wyeliminujesz niemożliwe, wszystko, co pozostanie, bez względu na to, jak nieprawdopodobne, musi być prawdą" i zdecyduj się na to zamiast zdania Dirka Gently′ego: "Gdy wyeliminujesz nieprawdopodobne , cokolwiek pozostaje, bez względu na to, jak niemożliwe, musi być prawdą."

Ostrożnie wybieraj swoje narzędzia

Jony do nowoczesnych zastosowań są bardzo rzadko budowane od podstaw. Są one montowane przy użyciu istniejących narzędzi - komponentów, bibliotek i frameworków - z kilku ważnych powodów:

•  Aplikacje rosną pod względem wielkości, złożoności i zaawansowania, a czas ich opracowywania skraca się. Pozwala to lepiej wykorzystać czas i inteligencję programistów, jeśli mogą skoncentrować się na pisaniu większej ilości kodu domeny biznesowej, a mniej kodu infrastruktury.

•  Szeroko używane komponenty i frameworki mogą mieć mniej błędów niż te opracowane wewnętrznie.

•  W sieci jest dużo darmowego oprogramowania wysokiej jakości, co oznacza niższe koszty rozwoju i większe prawdopodobieństwo znalezienia programistów z niezbędnymi zainteresowaniami i wiedzą.

•  Produkcja i konserwacja oprogramowania wymaga intensywnej pracy człowieka, więc kupowanie może być tańsze niż budowanie. Jednak wybór odpowiedniego zestawu narzędzi do Twojej aplikacji może być trudnym zadaniem wymagającym przemyślenia. Tak naprawdę przy dokonywaniu wyboru należy pamiętać o kilku rzeczach:

•  Różne narzędzia mogą opierać się na różnych założeniach dotyczących ich kontekstu - np. otaczającej infrastruktury, modelu sterowania, modelu danych, protokołów komunikacyjnych itp. - co może prowadzić do niezgodności architektonicznej między aplikacją a narzędziami. Taka niezgodność prowadzi do hacków i obejść, które sprawią, że kod będzie bardziej złożony niż to konieczne.

•  Różne narzędzia mają różne cykle życia, a uaktualnienie jednego z nich może stać się niezwykle trudnym i czasochłonnym zadaniem, ponieważ nowe funkcje, zmiany projektowe, a nawet poprawki błędów mogą powodować niekompatybilność z innymi narzędziami. Im większa liczba narzędzi, tym gorszy może być problem.

•  Niektóre narzędzia wymagają sporo konfiguracji, często za pomocą jednego lub więcej plików XML, które bardzo szybko mogą wymknąć się spod kontroli. Aplikacja może wyglądać tak, jakby była napisana w XML plus kilka nieparzystych linijek kodu w jakimś języku programowania. Złożoność konfiguracji sprawi, że aplikacja będzie trudna w utrzymaniu i rozbudowie.

•  Uzależnienie od dostawcy ma miejsce, gdy kod, który w dużym stopniu zależy od konkretnych produktów dostawców, jest przez nich ograniczany pod kilkoma względami: łatwość konserwacji, wydajność, zdolność do ewolucji, cena itp.

•  Jeśli planujesz używać wolnego oprogramowania, możesz odkryć, że wcale nie jest ono takie darmowe. Być może będziesz musiał kupić wsparcie komercyjne, które niekoniecznie będzie tanie.

•  Warunki licencji mają znaczenie, nawet w przypadku wolnego oprogramowania. Na przykład w niektórych firmach niedopuszczalne jest używanie oprogramowania licencjonowanego zgodnie z warunkami licencji GNU ze względu na jego wirusowy charakter - tzn. oprogramowanie opracowane z nim musi być rozpowszechniane wraz z kodem źródłowym.

Moją osobistą strategią łagodzenia tych problemów jest zaczynanie od małych rzeczy, używając tylko absolutnie niezbędnych narzędzi. Zwykle początkowy nacisk kładziony jest na wyeliminowanie konieczności angażowania się w programowanie infrastruktury niskiego poziomu (i problemów), np. poprzez użycie oprogramowania pośredniego zamiast używania surowych gniazd dla aplikacji rozproszonych. A w razie potrzeby dodaj więcej. Mam również tendencję do izolowania zewnętrznych narzędzi od obiektów mojej domeny biznesowej za pomocą interfejsów i warstw, dzięki czemu mogę zmienić narzędzie, jeśli zajdzie taka potrzeba, przy minimalnym wysiłku. Pozytywnym skutkiem ubocznym takiego podejścia jest to, że generalnie kończę z mniejszą aplikacją, która używa mniej narzędzi zewnętrznych niż pierwotnie przewidywano.

Kod w języku domeny

Wyobraź sobie dwie bazy kodów. W jednym natkniesz się na:

if (portfolioIdsByTraderId.get(trader.getId())
.containsKey(portfolio.getId())) {….}

Drapiesz się w głowę, zastanawiając się, do czego może służyć ten kod. Wygląda na to, że otrzymuje identyfikator od obiektu przedsiębiorcy; używając tego, aby uzyskać mapę z, cóż, mapof-map, najwyraźniej; a następnie sprawdzanie, czy na mapie wewnętrznej istnieje inny identyfikator z obiektu portfela. Jeszcze bardziej drapiesz się po głowie. Szukasz deklaracji portfolioIdsByTraderId i odkrywasz to:

Map> portfolioIdsByTraderId;

Stopniowo zdajesz sobie sprawę, że może to mieć coś wspólnego z tym, czy trader ma dostęp do określonego portfela. I oczywiście znajdziesz ten sam fragment wyszukiwania - lub, co bardziej prawdopodobne, podobny, ale nieco inny fragment kodu - zawsze, gdy coś obchodzi, czy trader ma dostęp do określonego portfela. W innej bazie kodu natkniesz się na to:

if (trader.canView(portfolio)) {…}

Bez drapania się po głowie. Nie musisz wiedzieć, skąd wie trader. Być może gdzieś w środku jest jedna z tych map-map. Ale to sprawa tradera, nie twoja. Teraz, w której z tych baz kodu wolałbyś pracować? Dawno, dawno temu mieliśmy tylko bardzo podstawowe struktury danych: bity, bajty i znaki (naprawdę tylko bajty, ale udajemy, że to litery i znaki interpunkcyjne). Ułamki dziesiętne były nieco skomplikowane, ponieważ nasze liczby o podstawie 10 nie działają zbyt dobrze w systemie binarnym, więc mieliśmy kilka rozmiarów typów zmiennoprzecinkowych. Potem pojawiły się tablice i łańcuchy (tak naprawdę po prostu różne tablice). Potem mieliśmy stosy, kolejki, skróty, listy połączone, listy pomijania i wiele innych ekscytujących struktur danych, które nie istnieją w prawdziwym świecie. "Informatyka" polegała na poświęceniu dużej ilości wysiłku na mapowanie świata rzeczywistego do naszych restrykcyjnych struktur danych. Prawdziwi guru pamiętali nawet, jak to zrobili. Następnie otrzymaliśmy typy zdefiniowane przez użytkownika! OK, to nie jest nowość, ale nieco zmienia grę. Jeśli Twoja domena zawiera koncepcje, takie jak handlowcy i portfele, możesz je modelować za pomocą typów nazywanych, powiedzmy, Trader i Portfolio. Ale co ważniejsze, możesz również modelować relacje między nimi za pomocą terminów domeny. Jeśli nie kodujesz przy użyciu terminów domenowych, tworzysz milczące (czytaj: tajne) zrozumienie, że to int tutaj oznacza sposób na zidentyfikowanie tradera, podczas gdy int tutaj oznacza sposób na zidentyfikowanie portfela. (Najlepiej ich nie pomylić!) A jeśli reprezentujesz koncepcję biznesową ("Niektórzy handlowcy nie mogą przeglądać niektórych portfeli - to nielegalne") z fragmentem algorytmicznym - powiedzmy, relacja istnienia na mapie kluczy - ty nie robią żadnych przysług dla audytorów i zgodności. Następny programista, który się pojawi, może nie być w tajemnicy, więc dlaczego nie ujawnić tego wprost? Używanie klucza jako odnośnika do innego klucza, który przeprowadza kontrolę istnienia, nie jest strasznie oczywiste. Jak ktoś ma wyczuć, gdzie wdrażane są reguły biznesowe zapobiegające konfliktowi interesów? Wyraźne koncepcje domeny w kodzie oznaczają, że inni programiści mogą znacznie łatwiej zebrać intencje kodu, niż próbując zmodernizować algorytm do tego, co rozumieją o domenie. Oznacza to również, że gdy model domeny ewoluuje - co nastąpi wraz ze wzrostem zrozumienia domeny - jesteś w dobrej pozycji, aby rozwinąć kod. W połączeniu z dobrą enkapsulacją istnieje duże prawdopodobieństwo, że reguła będzie istniała tylko w jednym miejscu i że można ją zmienić bez mądrzejszego kodu zależnego. Programista, który przyjdzie kilka miesięcy później, aby pracować nad kodem, podziękuje ci. Programistą, który pojawi się kilka miesięcy później, możesz być ty.

Kod to projekt

Wyobraź sobie, że budzisz się jutro i dowiadujesz się, że branża budowlana dokonała przełomu stulecia. Miliony tanich, niewiarygodnie szybkich robotów potrafią wytwarzać materiały z powietrza, mają prawie zerowy koszt energii i same się naprawiają. I jest coraz lepiej: biorąc pod uwagę jednoznaczny plan projektu budowlanego, roboty mogą go zbudować bez interwencji człowieka, a wszystko to przy znikomych kosztach. Można sobie wyobrazić wpływ na branżę budowlaną, ale co by się stało na wyższym poziomie? Jak zmieniłoby się zachowanie architektów i projektantów, gdyby koszty budowy były znikome? Obecnie modele fizyczne i komputerowe są budowane i rygorystycznie testowane przed zainwestowaniem w budowę. Czy przejmowalibyśmy się, gdyby budowa była zasadniczo darmowa? Jeśli projekt się zawali, to nic wielkiego - po prostu dowiedz się, co poszło nie tak i zbuduj kolejny. Są dalsze implikacje. Wraz z przestarzałymi modelami niedokończone projekty ewoluują poprzez wielokrotne budowanie i ulepszanie przybliżonego celu końcowego. Przypadkowy obserwator może mieć problem z odróżnieniem niedokończonego projektu od gotowego produktu. Nasza zdolność do przewidywania terminów zniknie. Koszty budowy łatwiej obliczyć niż koszty projektowe - znamy przybliżony koszt montażu dźwigara i ile dźwigarów potrzebujemy. Gdy przewidywalne zadania zmniejszają się do zera, zaczyna dominować mniej przewidywalny czas projektowania. Wyniki są uzyskiwane szybciej, ale wiarygodne terminy umykają. Oczywiście presja konkurencyjnej gospodarki nadal istnieje. Dzięki wyeliminowaniu kosztów budowy firma, która może szybko wykonać projekt, zyskuje przewagę na rynku. Szybkie wykonanie projektu staje się głównym naciskiem firm inżynieryjnych. Nieuchronnie ktoś, kto nie jest dogłębnie zaznajomiony z projektem, zobaczy niesprawdzoną wersję, dostrzeże przewagę rynkową wynikającą z wcześniejszego wydania i powie: "Wygląda wystarczająco dobrze". Niektóre projekty na śmierć i życie będą bardziej staranne, ale w wielu przypadkach konsumenci uczą się cierpieć z powodu niekompletnego projektu. Firmy zawsze mogą wysłać nasze magiczne roboty, aby "załatały" zniszczone budynki i pojazdy, które sprzedają. Wszystko to prowadzi do zaskakująco sprzecznego z intuicją wniosku: naszym jedynym założeniem było radykalne obniżenie kosztów budowy, a w rezultacie pogorszenie jakości. Nie powinno nas dziwić, że poprzednia historia rozgrywała się w oprogramowaniu. Jeśli przyjmiemy, że kod jest projektowaniem - procesem twórczym, a nie mechanicznym - kryzys oprogramowania zostanie wyjaśniony. Mamy teraz kryzys projektowy: zapotrzebowanie na sprawdzone projekty wysokiej jakości przekracza nasze możliwości ich tworzenia. Nacisk na stosowanie niekompletnego projektu jest silny. Na szczęście ten model oferuje również wskazówki, jak możemy stać się lepsi. Symulacje fizyczne są równoznaczne z testowaniem automatycznym; projektowanie oprogramowania nie jest kompletne, dopóki nie zostanie zweryfikowane za pomocą brutalnej serii testów. Aby uczynić takie testy bardziej efektywnymi, znajdujemy sposoby na ograniczenie ogromnej przestrzeni stanów dużych systemów. Udoskonalone języki i praktyki projektowania dają nam nadzieję. Wreszcie jest jeden nieunikniony fakt: wspaniałe projekty są tworzone przez wielkich projektantów, którzy poświęcają się mistrzostwu w swoim rzemiośle. Kod nie jest inny.

Sprawy dotyczące układu kodu

Wiele lat temu pracowałem nad systemem Cobol, w którym członkowie personelu nie mogli zmieniać wcięcia, chyba że mieli już powód, aby zmienić kod, ponieważ ktoś kiedyś coś zepsuł, wpuszczając linię do jednego z specjalne kolumny na początku wiersza. Działało to nawet wtedy, gdy układ był mylący, co czasami bywało, więc musieliśmy bardzo uważnie przeczytać kod, ponieważ nie mogliśmy mu ufać. Polityka musiała kosztować fortunę w przeciąganiu programisty. Istnieją badania sugerujące, że wszyscy spędzamy znacznie więcej czasu na programowaniu na nawigowaniu i czytaniu kodu, znajdowaniu miejsca, w którym należy dokonać zmiany, niż na pisaniu, więc właśnie pod tym kątem chcemy zoptymalizować. Oto trzy takie optymalizacje:

Łatwe do skanowania : Ludzie są naprawdę dobrzy w wizualnym dopasowywaniu wzorców (cecha pozostała z czasów, gdy musieliśmy dostrzec lwy na sawannie), więc mogę sobie pomóc, robiąc wszystko, co nie jest bezpośrednio związane z domeną - cała "przypadkowa złożoność" który jest dostępny w większości języków komercyjnych - znikaj w tle poprzez standaryzację. Jeśli kod, który zachowuje się tak samo, wygląda tak samo, mój system percepcyjny pomoże mi dostrzec różnice. Dlatego też przestrzegam konwencji dotyczących rozmieszczania części klasy w jednostce kompilacji: stałe, pola, metody publiczne, metody prywatne.

Ekspresyjny układ : Wszyscy nauczyliśmy się poświęcać czas na znalezienie właściwych nazw, aby nasz kod wyrażał jak najdokładniej to, co robi, a nie tylko wymieniać kroki - prawda? Częścią tej wyrazistości jest również układ kodu. Pierwszym krokiem jest uzgodnienie przez zespół automatycznego formatera dla podstaw, a następnie mogę wprowadzać poprawki ręcznie podczas kodowania. O ile nie ma aktywnej niezgody, zespół szybko zbliży się do wspólnego "ręcznie wykończonego" stylu. Formatter nie może zrozumieć moich intencji (powinienem wiedzieć, kiedyś taki napisałem) i jest dla mnie ważniejsze, aby podziały wierszy i grupowania odzwierciedlały intencje kodu, a nie tylko składnię języka. (Kevin McGuire uwolnił mnie z niewoli automatycznych formaterów kodu.)

Kompaktowy format : Im więcej mogę uzyskać na ekranie, tym więcej widzę bez łamania kontekstu przez przewijanie lub przełączanie plików, co oznacza, że mogę zachować mniej stanu w mojej głowie. Długie komentarze do procedur i dużo białych znaków miały sens w przypadku nazw ośmioznakowych i drukarek wierszowych, ale teraz żyję w środowisku IDE, które zajmuje się kolorowaniem składni i łączeniem krzyżowym. Piksele są moim czynnikiem ograniczającym, więc chcę, aby każdy przyczynił się do mojego zrozumienia kodu. Chcę, żeby układ pomógł mi zrozumieć kod, ale nic więcej.

Znajomy nieprogramista zauważył kiedyś, że kod wygląda jak poezja. Czuję to po naprawdę dobrym kodzie - że wszystko w tekście ma swój cel i że pomaga mi zrozumieć ideę. Niestety pisanie kodu nie ma takiego samego romantycznego wizerunku jak pisanie poezji.

Recenzje kodu

Powinieneś robić przeglądy kodu. Czemu? Ponieważ zwiększają jakość kodu i zmniejszają wskaźnik defektów. Ale niekoniecznie z powodów, o których myślisz. Ponieważ wcześniej mogli mieć złe doświadczenia z przeglądami kodu, wielu programistów nie lubi ich. Widziałem organizacje, które wymagają, aby cały kod przeszedł formalny przegląd przed wdrożeniem do produkcji. Często przegląd dokonuje architekt lub główny deweloper, co można opisać jako przegląd wszystkiego przez architekta. Jest to określone w podręczniku procesu tworzenia oprogramowania firmy, więc programiści muszą przestrzegać. Mogą istnieć organizacje, które potrzebują tak sztywnego i formalnego procesu, ale większość tego nie potrzebuje. W większości organizacji takie podejście przynosi efekt przeciwny do zamierzonego. Recenzenci mogą czuć się tak, jakby byli oceniani przez komisję ds. zwolnień warunkowych. Recenzenci potrzebują zarówno czasu na przeczytanie kodu, jak i czasu na zapoznanie się ze wszystkimi szczegółami systemu; mogą szybko stać się wąskim gardłem w tym procesie, a proces szybko ulega degeneracji. Zamiast po prostu poprawiać błędy w kodzie, celem przeglądów kodu powinno być dzielenie się wiedzą i ustalanie wspólnych wytycznych dotyczących kodowania. Udostępnianie kodu innym programistom umożliwia zbiorową własność kodu. Niech losowy członek zespołu przejdzie przez kod z resztą zespołu. Zamiast szukać błędów, powinieneś przejrzeć kod, próbując go poznać i zrozumieć. Bądź delikatny podczas przeglądów kodu. Upewnij się, że komentarze są konstruktywne, a nie żrące. Wprowadź różne role na spotkaniu przeglądowym, aby uniknąć wpływu starszeństwa organizacyjnego wśród członków zespołu na przegląd kodu. Przykłady ról mogą obejmować skupienie się jednego recenzenta na dokumentacji, innego na wyjątkach, a trzeciego na przyjrzeniu się funkcjonalności. Takie podejście pomaga rozłożyć ciężar przeglądu na członków zespołu. Każdego tygodnia regularnie przeprowadzaj przegląd kodu. Spędź kilka godzin na spotkaniu przeglądowym. Obracaj recenzenta na każdym spotkaniu w prosty sposób. Pamiętaj, aby zmieniać role między członkami zespołu również podczas każdego spotkania przeglądowego. Zaangażuj początkujących w przeglądy kodu. Mogą być niedoświadczeni, ale ich świeża wiedza uniwersytecka może zapewnić inną perspektywę. Zaangażuj ekspertów ze względu na ich doświadczenie i wiedzę. Szybciej i dokładniej zidentyfikują kod podatny na błędy. Przeglądy kodu będą przebiegać łatwiej, jeśli zespół ma konwencje kodowania, które są sprawdzane przez narzędzia. W ten sposób formatowanie kodu nigdy nie będzie omawiane podczas spotkania poświęconego przeglądowi kodu. Sprawienie, by recenzje kodu były zabawne, jest prawdopodobnie najważniejszym czynnikiem sukcesu. Recenzje dotyczą osób recenzujących. Jeśli spotkanie przeglądowe jest bolesne lub nudne, trudno będzie kogokolwiek zmotywować. Spraw, aby był to nieformalny przegląd kodu, którego głównym celem jest dzielenie się wiedzą między członkami zespołu. Zostaw sarkastyczne komentarze na zewnątrz i zamiast tego przynieś ciasto lub lunch w brązowej torebce.

Kodowanie z rozsądkiem

Próba ręcznego uzasadnienia poprawności oprogramowania skutkuje formalnym dowodem, który jest dłuższy niż kod i bardziej prawdopodobne, że zawiera błędy. Preferowane są narzędzia zautomatyzowane, ale nie zawsze są możliwe. Poniżej opisujemy ścieżkę środkową: półformalne rozumowanie o poprawności. Podstawowym podejściem jest podzielenie całego rozważanego kodu na krótkie odcinki - od pojedynczej linii, takiej jak wywołanie funkcji, do bloków zawierających mniej niż 10 linii - i spieranie się o ich poprawność. Argumenty muszą być tylko wystarczająco mocne, aby przekonać programistę swojego adwokata diabła. Sekcję należy wybrać tak, aby w każdym punkcie końcowym stan programu (mianowicie licznik programu i wartości wszystkich "żywych" obiektów) spełniał łatwo opisaną właściwość oraz aby funkcjonalność tej sekcji (przekształcenie stanu) łatwo opisać jako pojedyncze zadanie; wytyczne te uproszczą rozumowanie. Takie właściwości punktu końcowego uogólniają pojęcia, takie jak warunki wstępne i warunki końcowe dla funkcji oraz niezmienniki dla pętli i klas (w odniesieniu do ich wystąpień). Dążenie do tego, aby sekcje były jak najbardziej niezależne od siebie, upraszcza rozumowanie i jest niezbędne, gdy te sekcje mają być modyfikowane. Wiele praktyk kodowania, które są dobrze znane (choć być może mniej dobrze następnie) i uznane za "dobre" ułatwiają rozumowanie. W związku z tym, po prostu zamierzając uzasadnić swój kod, już zaczynasz zmierzać w kierunku lepszego stylu i struktury. Nic dziwnego, że większość z tych praktyk można sprawdzić za pomocą statycznych analizatorów kodu:

•  Unikaj używania instrukcji goto, ponieważ powodują one, że sekcje zdalne są od siebie wysoce zależne.
•  Unikaj używania modyfikowalnych zmiennych globalnych, ponieważ powodują one uzależnienie wszystkich sekcji, które ich używają.
•  Każda zmienna powinna mieć możliwie najmniejszy zakres. Na przykład obiekt lokalny można zadeklarować tuż przed jego pierwszym użyciem.
•  Uczyń obiekty niezmiennymi, gdy tylko jest to istotne.
•  Spraw, aby kod był czytelny, używając odstępów, zarówno poziomych, jak i pionowych - np. wyrównując powiązane struktury i używając pustej linii do oddzielenia dwóch sekcji.
•  Spraw, aby kod był samodokumentujący, wybierając opisowe (ale stosunkowo krótkie) nazwy obiektów, typów, funkcji itp.
•  Jeśli potrzebujesz sekcji zagnieżdżonej, ustaw ją jako funkcję.
•  Skróć swoje funkcje i skoncentruj się na jednym zadaniu. Stary limit 24 linii nadal obowiązuje. Chociaż rozmiar ekranu i rozdzielczość zmieniły się, nic nie zmieniło się w ludzkim poznaniu od lat 60. XX wieku.
•  Funkcje powinny mieć niewiele parametrów (cztery to dobra górna granica). Nie ogranicza to danych przekazywanych do funkcji: grupowanie powiązanych parametrów w jeden obiekt lokalizuje niezmienniki obiektu, co upraszcza wnioskowanie pod względem ich spójności i spójności.
•  Mówiąc bardziej ogólnie, każda jednostka kodu, od bloku do biblioteki, powinna mieć wąski interfejs. Mniejsza komunikacja zmniejsza wymagane rozumowanie. Oznacza to, że gettery, które zwracają stan wewnętrzny, są odpowiedzialnością - nie pytaj obiektu o informacje do pracy. Zamiast tego poproś obiekt, aby wykonał pracę z informacjami, które już posiada. Innymi słowy, enkapsulacja polega wyłącznie na wąskich interfejsach.
•  Aby zachować niezmienniki klas, należy odradzać stosowanie seterów. Setery mają tendencję do zezwalania na łamanie niezmienników, które rządzą stanem obiektu.
Oprócz uzasadnienia jego poprawności, kłótnie dotyczące kodu pomagają lepiej go zrozumieć. Przekazuj zdobyte spostrzeżenia z korzyścią dla wszystkich.

Komentarz do komentarzy

Na moich pierwszych zajęciach z programowania mój nauczyciel rozdał dwa arkusze kodowania BASIC. Na tablicy zadanie brzmiało: "Napisz program do wprowadzania i uśredniania 10 wyników w kręgle". Następnie nauczyciel wyszedł z pokoju. Jak trudne może to być? Nie pamiętam ostatecznego rozwiązania, ale jestem pewien, że miał w sobie pętlę FOR/NEXT i nie mógł mieć w sumie więcej niż 15 linii. Arkusze kodowania - dla was, dzieci, które to czytają, tak, kiedyś pisaliśmy kod odręcznie, zanim faktycznie wprowadziliśmy go do komputera - każda z nich mogła pomieścić około 70 linijek kodu. Byłem bardzo zdezorientowany, dlaczego nauczyciel dał nam dwa arkusze. Ponieważ moje pismo odręczne zawsze było okropne, użyłem drugiego, aby bardzo porządnie skopiować mój kod, mając nadzieję na zdobycie kilku dodatkowych punktów za styl. Ku mojemu zdziwieniu, kiedy otrzymałem zadanie z powrotem na początku następnych zajęć, otrzymałem ocenę ledwo pozytywną. (To miało być dla mnie omenem do końca mojego pobytu w szkole.) Na górze mojego starannie skopiowanego kodu nabazgrałem: "Brak komentarzy?" Nie wystarczyło, że oboje z nauczycielką wiedzieliśmy, co ma robić program. Częścią tego zadania było nauczenie mnie, że mój kod powinien wyjaśnić się następnemu programiście, który będzie za mną. To lekcja, której nie zapomniałem. Komentarze nie są złe. Są one tak samo potrzebne do programowania jak podstawowe konstrukcje rozgałęziające lub zapętlające. Większość współczesnych języków ma narzędzie podobne do javadoc, które analizuje poprawnie sformatowane komentarze, aby automatycznie zbudować dokument API. To bardzo dobry początek, ale niewystarczający. Wewnątrz twojego kodu powinny znajdować się wyjaśnienia dotyczące tego, co kod ma robić. Kodowanie według starego powiedzenia: "Jeśli trudno było pisać, powinno być trudne do odczytania", wyrządza krzywdę twojemu klientowi, pracodawcy, współpracownikom i przyszłemu ja. Z drugiej strony możesz posunąć się za daleko w swoim komentowaniu. Upewnij się, że Twoje komentarze wyjaśniają Twój kod, ale nie zaciemniają go. Posyp swój kod odpowiednimi komentarzami wyjaśniającymi, co ten kod ma osiągnąć. Twoje komentarze w nagłówku powinny dać każdemu programiście wystarczającą ilość informacji, aby użyć twojego kodu bez konieczności jego czytania, podczas gdy twoje komentarze w tekście powinny pomóc następnemu programiście w naprawieniu lub rozszerzeniu go. W jednym miejscu pracy nie zgodziłem się z decyzją projektową podjętą przez osoby nade mną. Czując się dość złośliwie, jak to często robią młodzi programiści, wkleiłem tekst e-maila z poleceniem użycia ich projektu do bloku komentarzy nagłówka pliku. Okazało się, że menedżerowie w tym konkretnym sklepie faktycznie przeglądali kod, gdy został on zatwierdzony. Było to moje pierwsze wprowadzenie do pojęcia ruchu ograniczającego karierę.

Komentuj tylko to, czego kod nie może powiedzieć

Różnica między teorią a praktyką jest większa w praktyce niż w teorii - obserwacja, która z pewnością dotyczy komentarzy. Teoretycznie ogólna idea komentowania kodu brzmi jak godna: zaoferuj czytelnikowi szczegóły, wyjaśnienie, co się dzieje. Co może być bardziej pomocne niż bycie pomocnym? W praktyce jednak komentarze często stają się plagą. Jak w przypadku każdej innej formy pisania, istnieje umiejętność pisania dobrych komentarzy. Duża część umiejętności polega na tym, by wiedzieć, kiedy ich nie pisać. Gdy kod jest źle sformułowany, kompilatory, interpretery i inne narzędzia będą pewne aby sprzeciwiać się. Jeśli kod jest w jakiś sposób niepoprawny funkcjonalnie, recenzje, analiza statyczna, testy i codzienne użytkowanie w środowisku produkcyjnym usunie większość błędów. Ale co z komentarzami? W książce The Elements of Programming Style Kernighan i Plauger zauważają, że "komentarz ma zerową (lub ujemną) wartość, jeśli jest błędny". A jednak takie komentarze często zaśmiecają i utrzymują się w kodzie w sposób, w jaki błędy kodowania nigdy nie mogłyby. Stanowią nieustanne źródło rozpraszania uwagi i dezinformacji, subtelny, ale nieustanny nacisk na myślenie programisty. A co z komentarzami, które nie są technicznie błędne, ale nie dodają wartości do kodu? Takie komentarze to szum. Komentarze, które powtarzają kod, nie oferują czytelnikowi niczego więcej - powtarzanie czegoś w kodzie raz za razem w języku naturalnym nie czyni tego prawdziwszym ani bardziej rzeczywistym. Zakomentowany kod nie jest kodem wykonywalnym, więc nie ma żadnego użytecznego wpływu ani na czytnik, ani na środowisko wykonawcze. Bardzo szybko czerstwieje. Komentarze związane z wersją i zakomentowany kod próbują odpowiedzieć na pytania dotyczące wersji i historii. Na te pytania odpowiedziały już (o wiele skuteczniej) narzędzia kontroli wersji. Przewaga hałaśliwych komentarzy i niepoprawnych komentarzy w bazie kodu zachęca programistów do ignorowania wszystkich komentarzy, pomijając je lub podejmując aktywne działania w celu ich ukrycia. Programiści są zaradni i rozwiążą wszystko, co uważa się za uszkodzenie: składanie komentarzy; przełączanie schematu kolorystycznego tak, aby komentarze i tło miały ten sam kolor; skrypty do filtrowania komentarzy. Aby uchronić bazę kodu przed takimi niewłaściwymi zastosowaniami pomysłowości programistów i zmniejszyć ryzyko przeoczenia jakichkolwiek komentarzy o prawdziwej wartości, komentarze powinny być traktowane tak, jakby były kodem. Każdy komentarz powinien wnieść jakąś wartość dla czytelnika, w przeciwnym razie to marnotrawstwo należy usunąć lub przepisać. Co zatem kwalifikuje się jako wartość? Komentarze powinny mówić coś, czego kod nie mówi i nie może powiedzieć. Komentarz wyjaśniający, co fragment kodu powinien już mówić, jest zaproszeniem do zmiany struktury kodu lub konwencji kodowania, aby kod mówił sam za siebie. Zamiast kompensować złe nazwy metod lub klas, zmień ich nazwy. Zamiast komentować sekcje w długich funkcjach, wyodrębnij mniejsze funkcje, których nazwy odzwierciedlają intencje poprzednich sekcji. Postaraj się wyrazić jak najwięcej za pomocą kodu. Wszelkie niedociągnięcia między tym, co możesz wyrazić w kodzie, a tym, co chciałbyś wyrazić w sumie, staje się wiarygodnym kandydatem na przydatny komentarz. Skomentuj to, czego kod nie może powiedzieć, a nie tylko to, czego nie mówi.

Kontynuacja nauczania

Żyjemy w ciekawych czasach. W miarę rozprzestrzeniania się rozwoju na całym świecie dowiadujesz się, że jest wielu ludzi zdolnych do wykonywania Twojej pracy. Musisz się uczyć, aby pozostać na rynku. W przeciwnym razie staniesz się dinozaurem, który utknął w tej samej pracy, aż pewnego dnia nie będziesz już potrzebny lub twoja praca zostanie zlecona do tańszego zasobu. Więc co z tym zrobisz? Niektórzy pracodawcy są wystarczająco hojni, aby zapewnić szkolenie, które poszerzy twoje umiejętności. Inni mogą w ogóle nie być w stanie oszczędzić czasu lub pieniędzy na jakiekolwiek szkolenie. Aby grać bezpiecznie, musisz wziąć odpowiedzialność za własną edukację. Oto lista sposobów, dzięki którym będziesz się uczyć. Wiele z nich można znaleźć w Internecie za darmo:

•  Czytaj książki, czasopisma, blogi, kanały na Twitterze i strony internetowe. Jeśli chcesz zagłębić się w temat, rozważ dołączenie do listy dyskusyjnej lub grupy dyskusyjnej.
•  Jeśli naprawdę chcesz zagłębić się w technologię, weź udział - napisz trochę kodu.
•  Zawsze staraj się współpracować z mentorem, ponieważ bycie najlepszym facetem może utrudnić twoją edukację. Chociaż możesz nauczyć się czegoś od każdego, możesz nauczyć się o wiele więcej od kogoś mądrzejszego lub bardziej doświadczonego od Ciebie. Jeśli nie możesz znaleźć mentora, rozważ przejście dalej.
•  Korzystaj z wirtualnych mentorów. Znajdź w sieci autorów i programistów, których naprawdę lubisz, i czytaj wszystko, co piszą. Subskrybuj ich blogi.
•  Poznaj frameworki i biblioteki, z których korzystasz. Wiedząc, jak coś działa, wiesz, jak lepiej to wykorzystać. Jeśli są open source, naprawdę masz szczęście. Użyj debugera, aby przejść przez kod, aby zobaczyć, co dzieje się pod maską. Zobaczysz kod napisany i zrecenzowany przez naprawdę mądrych ludzi.
•  Za każdym razem, gdy popełnisz błąd, naprawisz błąd lub napotkasz problem, spróbuj naprawdę zrozumieć, co się stało. Prawdopodobnie ktoś inny natknął się na ten sam problem i umieścił go w sieci. Google jest tutaj naprawdę przydatny.
•  Dobrym sposobem na nauczenie się czegoś jest nauczanie lub mówienie o tym. Kiedy ludzie będą cię słuchać i zadawać pytania, będziesz bardzo zmotywowany do nauki. Wypróbuj lunch i ucz się w pracy, grupie użytkowników lub lokalnej konferencji.
•  Dołącz lub załóż grupę badawczą (społeczność wzorową) lub lokalną grupę użytkowników dla interesującego Cię języka, technologii lub dyscypliny.
•  Jedź na konferencje. A jeśli nie możesz iść, wiele konferencji udostępnia swoje przemówienia online za darmo.
•  Długie dojazdy? Słuchaj podcastów.
•  Czy kiedykolwiek uruchamiałeś statyczne narzędzie do analizy kodu lub przeglądałeś ostrzeżenia w swoim środowisku IDE? Dowiedz się, co zgłaszają i dlaczego.
•  Postępuj zgodnie z radami pragmatycznych programistów* i co roku ucz się nowego języka. Przynajmniej naucz się nowej technologii lub narzędzia. Rozgałęzienie daje Ci nowe pomysły, które możesz wykorzystać w swoim obecnym stosie technologicznym.
•  Nie wszystko, czego się uczysz, musi dotyczyć technologii. Poznaj domenę, w której pracujesz, aby lepiej zrozumieć wymagania i pomóc w rozwiązaniu problemu biznesowego. Uczenie się, jak być bardziej produktywnym - jak lepiej pracować - to kolejna dobra opcja.
•  Wracaj do szkoły.

Byłoby miło mieć możliwości, które Neo miał w Matrixie i po prostu pobierać potrzebne nam informacje do naszych mózgów. Ale tego nie robimy, więc zajmie to trochę czasu. Nie musisz spędzać każdej wolnej godziny na nauce. Trochę czasu - powiedzmy co tydzień - jest lepsze niż nic. Istnieje (lub powinno być) życie poza pracą. Technologia szybko się zmienia. Nie zostawaj w tyle.

Wygoda nie jest -ility

Wiele powiedziano o znaczeniu i wyzwaniach związanych z projektowaniem dobrych interfejsów API. Trudno za pierwszym razem, a jeszcze trudniej jest zmienić później - trochę jak wychowywanie dzieci. Większość doświadczonych programistów nauczyła się, że dobre API podąża za spójnym poziomem abstrakcji, wykazuje spójność i symetrię oraz tworzy słownictwo dla ekspresyjnego języka. Niestety, świadomość zasad przewodnich nie przekłada się automatycznie na odpowiednie zachowanie. Jedzenie słodyczy jest dla ciebie złe. Zamiast wygłaszać kazania z góry, chcę wybrać konkretną "strategię" projektowania API, z którą spotykam się od czasu do czasu: argument z wygody. Zwykle zaczyna się od jednego z następujących "wglądów":

•  Nie chcę, aby inne klasy musiały wykonać dwa osobne wywołania, żeby zrobić tę jedną rzecz.
•  Dlaczego powinienem wykonać inną metodę, jeśli jest prawie taka sama jak ta metoda? Dodam tylko prosty przełącznik.
•  Widzisz, to bardzo proste: jeśli drugi parametr ciągu kończy się na ".txt", metoda automatycznie zakłada, że pierwszy parametr to nazwa pliku, więc naprawdę nie potrzebuję dwóch metod.

Chociaż dobrze zamierzone, takie argumenty mają tendencję do zmniejszania czytelności kodu przy użyciu interfejsu API. Wywołanie metody, takie jak:

parser.processNodes(text, false);

jest praktycznie bez znaczenia bez znajomości implementacji lub przynajmniej zapoznania się z dokumentacją. Ta metoda została prawdopodobnie zaprojektowana dla wygody realizatora, w przeciwieństwie do wygody rozmówcy - "Nie chcę, aby rozmówca musiał wykonywać dwa oddzielne wywołania" przetłumaczone na "Nie chciałem kodować dwóch oddzielnych metod ". Nie ma nic fundamentalnie złego w wygodzie, jeśli ma być antidotum na nudę, niezdarność lub niezręczność. Jeśli jednak zastanowimy się nad tym trochę uważniej, antidotum na te objawy to skuteczność, konsekwencja i elegancja, niekoniecznie wygoda. Interfejsy API mają ukrywać podstawową złożoność, więc możemy realistycznie oczekiwać, że dobry projekt API będzie wymagał pewnego wysiłku. Pojedyncza duża metoda z pewnością mogłaby być wygodniejsza do napisania niż dobrze przemyślany zestaw operacji, ale czy byłaby łatwiejsza w użyciu? Metafora API jako języka może poprowadzić nas w kierunku lepszych decyzji projektowych w takich sytuacjach. Interfejs API powinien zapewniać ekspresyjny język, który daje kolejną warstwę powyżej wystarczającego słownictwa, aby zadawać przydatne pytania i odpowiadać na nie. Nie oznacza to, że powinno zawierać dokładnie jedną metodę lub czasownik dla każdego pytania, które warto zadać. Różnorodne słownictwo pozwala nam wyrażać subtelności w znaczeniu. Na przykład wolimy mówić biegać zamiast chodzić(prawda), mimo że może być postrzegana jako zasadniczo ta sama operacja, po prostu wykonywana z różnymi prędkościami. Spójny i przemyślany słownik interfejsu API sprawia, że w kolejnej warstwie kod jest ekspresyjny i łatwy do zrozumienia. Co ważniejsze, słownictwo komponowalne pozwala innym programistom na korzystanie z API w sposób, którego być może nie przewidziałeś - jest to rzeczywiście wielka wygoda dla użytkowników API! Następnym razem, gdy będziesz kuszony, aby zebrać kilka rzeczy w jedną metodę API, pamiętaj, że język angielski nie ma jednego słowa na MakeUpYourRoomBeQuietAndDoYourHomeWork, mimo że byłoby to naprawdę wygodne przy tak często wymaganej operacji.

Wdrażaj wcześnie i często

Debugowanie procesów wdrażania i instalacji jest często odkładane do końca projektu. W niektórych projektach pisanie narzędzi instalacyjnych jest delegowane do inżyniera wydania, który przyjmuje to zadanie jako "zło konieczne". Recenzje i demonstracje są wykonywane w ręcznie wykonanym środowisku, aby zapewnić, że wszystko działa. W rezultacie zespół nie ma doświadczenia z procesem wdrażania ani wdrożonym środowiskiem, dopóki nie może być za późno na wprowadzenie zmian. Proces instalacji/wdrożenia jest pierwszą rzeczą, którą widzi klient, a prosty to pierwszy krok do posiadania niezawodnego (lub przynajmniej łatwego do debugowania) środowiska produkcyjnego. Klient będzie używał wdrożonego oprogramowania. Nie upewniając się, że wdrożenie prawidłowo skonfiguruje aplikację, będziesz zadawał pytania swoim klientom, zanim będą mogli dokładnie korzystać z oprogramowania. Rozpoczęcie projektu od procesu instalacji da ci czas na ewolucję procesu w miarę przechodzenia przez cykl rozwoju produktu, a także szansę na wprowadzenie zmian w kodzie aplikacji, aby ułatwić instalację. Okresowe uruchamianie i testowanie procesu instalacji w czystym środowisku zapewnia również sprawdzenie, czy w kodzie nie przyjęto założeń, które opierają się na środowiskach programistycznych lub testowych. Umieszczenie wdrożenia na końcu oznacza, że proces wdrażania może być bardziej skomplikowany, aby obejść założenia w kodzie. To, co wydawało się świetnym pomysłem w IDE, w którym masz pełną kontrolę nad środowiskiem, może znacznie skomplikować proces wdrażania. Lepiej poznać wszystkie kompromisy wcześniej niż później. Chociaż "możliwość wdrożenia" nie wydaje się mieć dużej wartości biznesowej na wczesnym etapie w porównaniu z obserwowaniem aplikacji działającej na laptopie programisty, prawda jest taka, że dopóki nie będziesz w stanie zademonstrować swojej aplikacji w środowisku docelowym, istnieje dużo pracy do wykonania, zanim będziesz mógł zapewnić wartość biznesową. Jeśli Twoim uzasadnieniem dla odkładania procesu wdrażania jest to, że jest on trywialny, zrób to mimo wszystko, ponieważ jest to niski koszt. Jeśli jest to zbyt skomplikowane lub jeśli jest zbyt wiele niepewności, zrób to, co zrobiłbyś z kodem aplikacji: eksperymentuj, oceniaj i refaktoryzuj proces wdrażania na bieżąco. Proces instalacji/wdrażania ma zasadnicze znaczenie dla produktywności Twoich klientów lub zespołu usług profesjonalnych, dlatego powinieneś testować i refaktoryzować ten proces na bieżąco. Testujemy i refaktoryzujemy kod źródłowy w całym projekcie. Wdrożenie zasługuje na nie mniej.

Odróżnij wyjątki biznesowe od technicznych

Zasadniczo istnieją dwa powody, dla których coś idzie nie tak w czasie wykonywania: problemy techniczne, które uniemożliwiają nam korzystanie z aplikacji oraz logika biznesowa, która uniemożliwia nam niewłaściwe korzystanie z aplikacji. Większość współczesnych języków, takich jak LISP, Java, Smalltalk i C#, używa wyjątków do sygnalizowania obu tych sytuacji. Jednak te dwie sytuacje są tak różne, że należy je ostrożnie rozdzielić. Potencjalnym źródłem nieporozumień jest reprezentowanie ich obu przy użyciu tej samej hierarchii wyjątków, nie wspominając o tej samej klasie wyjątków. W przypadku błędu programowania może wystąpić nierozwiązywalny problem techniczny. Na przykład, jeśli próbujesz uzyskać dostęp do elementu 83 z tablicy o rozmiarze 17, to program jest wyraźnie zgubiony i powinien pojawić się jakiś wyjątek. Wersja subtelniejsza wywołuje jakiś kod biblioteki z nieodpowiednimi argumentami, powodując tę samą sytuację wewnątrz biblioteki. Błędem byłaby próba rozwiązania tych sytuacji, które sam spowodowałeś. Zamiast tego pozwalamy bąbelkowi wyjątku do najwyższego poziomu architektury i pozwalamy, aby jakiś ogólny mechanizm obsługi wyjątków zrobił wszystko, co w jego mocy, aby zapewnić, że system jest w bezpiecznym stanie, na przykład wycofywanie transakcji, rejestrowanie i administrowanie alertami oraz raportowanie z powrotem (uprzejmie) do użytkownika. Wariantem tej sytuacji jest sytuacja, gdy znajdujesz się w "sytuacji bibliotecznej", a wywołujący złamał kontrakt Twojej metody, np. przekazując całkowicie dziwny argument lub nie mając odpowiednio skonfigurowanego obiektu zależnego. Jest to na równi z dostępem do 83. elementu z 17: dzwoniący powinien był sprawdzić; nie zrobienie tego jest błędem programisty po stronie klienta. Właściwą odpowiedzią jest zgłoszenie wyjątku technicznego. Inna, ale wciąż techniczna sytuacja ma miejsce, gdy program nie może działać z powodu problemu w środowisku wykonawczym, takiego jak nieodpowiadająca baza danych. W tej sytuacji musisz założyć, że infrastruktura zrobiła to, co mogło rozwiązać problem - naprawiając połączenia i ponawiając rozsądną liczbę razy - i nie powiodło się. Nawet jeśli przyczyna jest inna, sytuacja z kodem wywołującym jest podobna: niewiele może na to poradzić. Tak więc sygnalizujemy sytuację za pomocą wyjątku, który pozwala na przejście do ogólnego mechanizmu obsługi wyjątków. W przeciwieństwie do nich mamy sytuację, w której nie można zakończyć połączenia z powodu logicznej domeny. W tym przypadku spotkaliśmy się z sytuacją stanowiącą wyjątek, tj. niezwykłą i niepożądaną, ale nie dziwaczną lub programowo błędną (np. jeśli próbuję wypłacić pieniądze z konta, na którym nie ma wystarczających środków). Innymi słowy, tego rodzaju sytuacja jest częścią kontraktu, a wyrzucenie wyjątku jest tylko alternatywną ścieżką powrotu, która jest częścią modelu i o której klient powinien być świadomy i przygotowany do obsługi. W takich sytuacjach należy utworzyć określony wyjątek lub oddzielną hierarchię wyjątków, aby klient mógł obsłużyć sytuację na własnych warunkach. Mieszanie wyjątków technicznych i wyjątków biznesowych w tej samej hierarchii zaciera rozróżnienie i dezorientuje wywołującego co do kontraktu metody jest to, jakie warunki należy zapewnić przed wezwaniem i z jakimi sytuacjami ma sobie radzić. Oddzielenie spraw zapewnia przejrzystość i zwiększa szanse, że wyjątki techniczne będą obsługiwane przez niektóre ramy aplikacji, podczas gdy wyjątki domeny biznesowej są w rzeczywistości uwzględniane i obsługiwane przez kod klienta.

Wykonuj dużo celowej praktyki

Rozważna praktyka nie polega po prostu na wykonywaniu zadania. Jeśli zadajesz sobie pytanie: "Dlaczego wykonuję to zadanie?" a twoja odpowiedź brzmi: "Aby wykonać zadanie", wtedy nie wykonujesz celowej praktyki. Celowo ćwiczysz, aby poprawić swoją zdolność do wykonania zadania. Chodzi o umiejętności i technikę. Celowa praktyka oznacza powtarzanie. Oznacza to wykonywanie zadania w celu zwiększenia opanowania jednego lub więcej aspektów zadania. Oznacza to powtórzenie powtórki. Powoli, w kółko, aż osiągniesz pożądany poziom mistrzostwa. Celowo ćwiczysz, aby opanować zadanie, a nie ukończyć zadanie. Głównym celem płatnego rozwoju jest ukończenie produktu, podczas gdy głównym celem świadomej praktyki jest poprawa wyników. One nie są takie same. Zadaj sobie pytanie, ile czasu poświęcasz na rozwijanie cudzego produktu? Jak bardzo się rozwijasz? Ile świadomej praktyki potrzeba, aby zdobyć wiedzę specjalistyczną?

•  Peter Norvig pisze*, że "może być tak, że 10 000 godzin… to magia liczb."

•  W Leading Lean Software Development , Mary Poppendieck zauważa, że "wystarczy elitarnym wykonawcom co najmniej 10 000 godzin celowej, skoncentrowanej praktyki, aby stać się ekspertami".

Ekspertyza przybywa stopniowo z biegiem czasu - nie od razu w ciągu 10 000 godzin! Niemniej jednak 10 000 godzin to dużo: około 20 godzin tygodniowo przez 10 lat. Biorąc pod uwagę ten poziom zaangażowania, możesz się martwić, że po prostu nie jesteś materiałem eksperckim. Jesteś. Wielkość to w dużej mierze kwestia świadomego wyboru. Twój wybór. Badania z ostatnich dwóch dekad wykazały, że głównym czynnikiem zdobywania wiedzy jest czas poświęcony na świadomą praktykę. Zdolność wrodzona nie jest głównym czynnikiem. Według Mary Poppendieck:

Wśród badaczy wyników ekspertów panuje powszechna zgoda, że wrodzony talent nie stanowi znacznie więcej niż próg; musisz mieć minimalną naturalną zdolność, aby zacząć uprawiać sport lub zawód. Po tym ludzie, którzy się wyróżniają, to ci, którzy pracują najciężej.

Nie ma sensu celowo ćwiczyć czegoś, w czym już jesteś ekspertem. Celowa praktyka oznacza praktykowanie czegoś, w czym nie jesteś dobry.

Peter Norvig wyjaśnia:

Kluczem [do rozwijania wiedzy specjalistycznej] jest praktyka przemyślana: nie tylko robienie tego w kółko, ale stawianie sobie zadania, które przekracza twoje obecne możliwości, próbowanie go, analizowanie wyników w trakcie i po wykonaniu oraz korygowanie wszelkich błędów.

A Mary Poppendieck pisze:

Celowa praktyka nie oznacza robienia tego, w czym jesteś dobry; oznacza stawianie sobie wyzwań, robienie tego, w czym nie jesteś dobry. Więc to niekoniecznie jest zabawne.

Celowa praktyka polega na uczeniu się, uczeniu się, które cię zmienia, uczeniu się, które zmienia twoje zachowanie. Powodzenia.

Języki specyficzne dla domeny

Za każdym razem, gdy słuchasz dyskusji ekspertów z dowolnej dziedziny, bądź to szachiści, nauczyciele przedszkolni czy agenci ubezpieczeniowi, zauważysz, że ich słownictwo różni się znacznie od języka potocznego. To jest część tego, czym są języki specyficzne dla domeny (DSL): określona domena ma specjalistyczne słownictwo do opisania rzeczy, które są specyficzne dla tej domeny. W świecie oprogramowania DSL dotyczą wyrażeń wykonywalnych w języku specyficzne dla domeny, wykorzystujące ograniczone słownictwo i gramatykę, które są czytelne, zrozumiałe i - miejmy nadzieję - zapisywalne przez ekspertów dziedzinowych. DSL skierowane do twórców oprogramowania lub naukowców istnieją od dawna. Jednymi ze starszych przykładów są uniksowe "małe języki" znalezione w plikach konfiguracyjnych i języki utworzone za pomocą makr LISP. DSL są powszechnie klasyfikowane jako wewnętrzne lub zewnętrzne:

Wewnętrzne DSL : Są napisane w języku programowania ogólnego przeznaczenia, którego składnia została wygięta, aby bardziej przypominać język naturalny. Jest to łatwiejsze w przypadku języków, które oferują więcej cukru składniowego i możliwości formatowania (np. Ruby i Scala) niż w przypadku innych, które tego nie robią (np. Java). Większość wewnętrznych DSL otacza istniejące interfejsy API, biblioteki lub kod biznesowy i zapewnia opakowanie dla mniej wymagającego dostępu do funkcji. Są bezpośrednio wykonywalne, po prostu je uruchamiając. W zależności od implementacji i domeny służą do budowania struktur danych, definiowania zależności, uruchamiania procesów lub zadań, komunikacji z innymi systemami czy walidacji danych wprowadzanych przez użytkownika. Składnia wewnętrznego DSL jest ograniczona przez język hosta. Istnieje wiele wzorców - np. konstruktor wyrażeń, łączenie metod i adnotacja - które mogą pomóc w dostosowaniu języka hosta do DSL. Jeśli język hosta nie wymaga rekompilacji, można dość szybko opracować wewnętrzne łącze DSL, współpracując z ekspertem domeny.

Zewnętrzne DSL : są tekstowymi lub graficznymi wyrażeniami języka, chociaż tekstowe DSL są bardziej powszechne niż graficzne. Wyrażenia tekstowe mogą być przetwarzane przez łańcuch narzędzi, który obejmuje lekser, parser, transformator modelowy, generatory i dowolny inny rodzaj przetwarzania końcowego. Zewnętrzne łącza DSL są najczęściej wczytywane do modeli wewnętrznych, które stanowią podstawę do dalszego przetwarzania. Pomocne jest zdefiniowanie gramatyki (np. w EBNF). Gramatyka stanowi punkt wyjścia do generowania części łańcucha narzędzi (np. edytor, wizualizator, generator parserów). W przypadku prostych DSL może wystarczyć ręcznie wykonany parser - używając np. wyrażeń regularnych. Niestandardowe parsery mogą stać się nieporęczne, jeśli wymaga się od nich zbyt wiele, więc warto przyjrzeć się narzędziom zaprojektowanym specjalnie do pracy z gramatykami językowymi i DSL, np. openArchitectureWare, ANTLR, SableCC, AndroMDA. Definiowanie zewnętrznych DSL jako dialektów XML jest również dość powszechne, chociaż czytelność jest często problemem - zwłaszcza dla czytelników nietechnicznych.

Musisz zawsze brać pod uwagę grupę docelową swojego DSL. Czy są to programiści, menedżerowie, klienci biznesowi czy użytkownicy końcowi? Musisz dostosować poziom techniczny języka, dostępne narzędzia, pomoc składni (np. IntelliSense), wczesną walidację, wizualizację i reprezentację do zamierzonych odbiorców. Ukrywając szczegóły techniczne, DSL mogą wzmocnić użytkowników, dając im możliwość dostosowania systemów do ich potrzeb bez konieczności pomocy programistów. Może również przyspieszyć rozwój ze względu na potencjalną dystrybucję pracy po wprowadzeniu początkowej struktury językowej. Język można rozwijać stopniowo. Dostępne są również różne ścieżki migracji dla istniejących wyrażeń i gramatyk.

Nie bój się zepsuć rzeczy

Każdy, kto ma doświadczenie w branży, bez wątpienia pracował nad projektem, w którym baza kodu była w najlepszym razie niepewna. System jest słabo uwzględniony, a zmiana jednej rzeczy zawsze prowadzi do złamania innej niepowiązanej funkcji. Za każdym razem, gdy dodawany jest moduł, celem kodera jest jak najmniej zmian i wstrzymanie oddechu podczas każdego wydania. Jest to programowy odpowiednik gry Jenga z dwuteownikami w wieżowcu i jest skazany na katastrofę. Powodem, dla którego wprowadzanie zmian jest tak denerwujące, jest to, że system jest chory. Potrzebuje lekarza, w przeciwnym razie jego stan tylko się pogorszy. Ty już wiesz, co jest nie tak z twoim systemem, ale boisz się rozbić jajka, aby zrobić omlet. Wykwalifikowany chirurg wie, że przed operacją trzeba wykonać nacięcia, ale wie też, że są one tymczasowe i będą się goić. Efekt końcowy operacji jest wart początkowego bólu, a pacjent powinien wyleczyć się do lepszego stanu niż był przed operacją. Nie bój się swojego kodu. Kogo to obchodzi, jeśli coś się chwilowo zepsuje, gdy będziesz je przenosić? Paraliżujący strach przed zmianą spowodował, że twój projekt znalazł się w tym stanie. Zainwestowanie czasu w refaktoryzację zwróci się kilkakrotnie w całym cyklu życia projektu. Dodatkową korzyścią jest to, że doświadczenie Twojego zespołu w radzeniu sobie z chorym systemem sprawia, że wszyscy jesteście ekspertami w wiedzy, jak powinien działać. Zastosuj tę wiedzę, zamiast ją odrzucać. Praca nad systemem, którego nienawidzisz, nie jest sposobem na spędzanie czasu. Zdefiniuj na nowo interfejsy wewnętrzne, zrestrukturyzuj moduły, zrefaktoryzuj kod kopiuj-wklej i uprość swój projekt, zmniejszając zależności. Możesz znacznie zmniejszyć złożoność kodu, eliminując narożne przypadki, które często wynikają z niewłaściwie połączonych funkcji. Powoli przenieś starą strukturę do nowej, testując po drodze. Próba osiągnięcia dużej refaktoryzacji w "jednym wielkim huku" spowoduje wystarczająco dużo problemów, abyś rozważył porzucenie całego wysiłku w połowie. Bądź chirurgiem, który nie boi się wyciąć chorych części, aby zrobić miejsce na uzdrowienie. Ta postawa jest zaraźliwa i zainspiruje innych do rozpoczęcia pracy nad projektami porządkowymi, które odkładali. Prowadź listę "higienicznych" zadań, które zdaniem zespołu są warte zachodu dla ogólnego dobra projektu. Przekonaj kierownictwo, że nawet jeśli te zadania mogą nie przynosić widocznych rezultatów, zmniejszą wydatki i przyspieszą przyszłe wydania. Nigdy nie przestawaj dbać o ogólne "zdrowie" kodu.

Nie bądź słodki ze swoimi danymi testowymi

Robiło się późno. Wrzuciłem kilka danych zastępczych, aby przetestować układ strony, nad którym pracowałem.
Na nazwiska użytkowników przywłaszczyłem sobie członków The Clash. Nazwy firm? Wystarczą tytuły piosenek Sex Pistols. Teraz potrzebowałem kilku symboli giełdowych - tylko kilku czteroliterowych słów pisanych wielkimi literami.
Użyłem tych czteroliterowych słów.
Wydawało się to nieszkodliwe. Po prostu coś dla siebie i może innych programistów następnego dnia przed podłączeniem prawdziwego źródła danych.
Następnego ranka kierownik projektu zrobił kilka zrzutów ekranu do prezentacji.

Historia programowania jest zaśmiecona tego rodzaju opowieściami wojennymi. Rzeczy, które programiści i projektanci zrobili "czego nikt inny nie zobaczy", co niespodziewanie stało się widoczne. Rodzaj wycieku może się różnić, ale gdy już się zdarzy, może być śmiertelny dla osoby, zespołu lub firmy odpowiedzialnej. Przykłady zawierają:

•  Podczas spotkania dotyczącego statusu klient klika przycisk, który nie został jeszcze zaimplementowany. Mówi się mu: "Nie klikaj tego ponownie, kretynie".
•  Programista utrzymujący przestarzały system został poinstruowany, aby dodać okno dialogowe błędu i postanawia użyć danych wyjściowych istniejącego zakulisowego rejestrowania do jego zasilania. Użytkownicy nagle stają w obliczu komunikatów, takich jak "Niepowodzenie zatwierdzenia świętej bazy danych, Batmanie!" kiedy coś się zepsuje.
•  Ktoś miesza interfejs testowy i administracyjny na żywo i wprowadza "śmieszne" dane. Klienci widzą w Twoim sklepie internetowym "osobisty masażer w kształcie Billa Gatesa" o wartości 1 miliona USD.

Aby przyswoić stare powiedzenie, że "kłamstwo może podróżować przez pół świata, podczas gdy prawda wkłada buty", w dzisiejszych czasach, wpadką może być Dugg, Twitter i Flibflarbed przed kimkolwiek w strefie czasowej dewelopera nie śpi, żeby coś z tym zrobić. Nawet twój kod źródłowy niekoniecznie jest wolny od kontroli. W 2004 roku, gdy paczka z kodem źródłowym Windows 2000 trafiła do sieci wymiany plików, niektórzy ludzie radośnie przeszukiwali ją w poszukiwaniu wulgaryzmów, obelg i innych zabawnych treści. muszę przyznać, że od czasu do czasu został przeze mnie zawłaszczony!) Podsumowując, pisząc dowolny tekst w swoim kodzie - czy to komentarze, logowanie, dialogi, czy dane testowe - zawsze zadawaj sobie pytanie, jak będzie wyglądał, gdy stanie się publiczny . Uratuje kilka czerwonych twarzy dookoła.

Nie ignoruj tego błędu!

Pewnego wieczoru szedłem ulicą, żeby spotkać się z przyjaciółmi w barze. Od jakiegoś czasu nie piliśmy piwa i nie mogłem się doczekać, kiedy znów ich zobaczę. W pośpiechu nie patrzyłem, dokąd idę. Potknęłam się o krawędź krawężnika i wylądowałam płasko na twarzy. Cóż, wydaje mi się, że dobrze mi to służy, że nie zwracam uwagi.

Bolało mnie noga, ale spieszyłam się na spotkanie z przyjaciółmi. Więc podciągnąłem się i kontynuowałem. Gdy szedłem dalej, ból się nasilał. Chociaż początkowo odrzuciłem to jako szok, szybko zdałem sobie sprawę, że coś jest nie tak.

Ale mimo to pospieszyłem do baru. Byłem w agonii, zanim przyjechałem. Nie miałem wspaniałego wieczoru, bo byłem strasznie rozkojarzony. Rano poszedłem do lekarza i dowiedziałem się, że złamałem kość piszczelową. Gdybym przestał, gdy poczułem ból, zapobiegłbym wielu dodatkowym szkodom, które spowodowałem, chodząc po nim. Prawdopodobnie najgorszy poranek po moim życiu.

Zbyt wielu programistów pisze kod jak moja katastrofalna noc. Błąd, jaki błąd? To nie będzie poważne. Szczerze mówiąc. Mogę to zignorować. To nie jest zwycięska strategia dla solidnego kodu. W rzeczywistości to zwykłe lenistwo. (Niewłaściwy rodzaj.) Bez względu na to, jak mało prawdopodobne wydaje Ci się wystąpienie błędu w Twoim kodzie, zawsze powinieneś go sprawdzić i zawsze go obsłużyć. Za każdym razem. Nie oszczędzasz czasu, jeśli tego nie robisz; gromadzisz potencjalne problemy na przyszłość. Błędy w naszym kodzie zgłaszamy na kilka sposobów, w tym: •  Kody powrotne mogą być użyte jako wynikowa wartość funkcji oznaczająca "to nie zadziałało". Kody powrotu błędów są zbyt łatwe do zignorowania. Nie zobaczysz niczego w kodzie, aby podkreślić problem. Rzeczywiście, ignorowanie wartości zwracanych przez niektóre standardowe funkcje języka C stało się normalną praktyką. Jak często sprawdzasz wartość zwracaną z printf?
•  errno to ciekawa aberracja C, osobna zmienna globalna ustawiona na sygnalizację błędu. Jest łatwy do zignorowania, trudny w użyciu i prowadzi do różnego rodzaju nieprzyjemnych problemów - na przykład, co się dzieje, gdy masz wiele wątków wywołujących tę samą funkcję? Niektóre platformy izolują cię tutaj od bólu; inni nie.
•  Wyjątki to bardziej ustrukturyzowany, obsługiwany językowo sposób sygnalizowania i obsługi błędów. I nie możesz ich zignorować. Czy możesz? Widziałem dużo takiego kodu:

try {
// …Zrób coś…
}
catch (…) {} // zignoruj błędy

Zbawcza łaska tej okropnej konstrukcji polega na tym, że podkreśla ona fakt, że robisz coś moralnie wątpliwego. Jeśli zignorujesz błąd, przymkniesz oko i udasz, że nic nie poszło nie tak, ryzykujesz. Tak jak moja noga znalazła się w gorszym stanie, niż gdybym natychmiast przestał na niej chodzić, orka bez względu na czerwone flagi może prowadzić do bardzo złożonych niepowodzeń. Rozwiąż problemy przy najbliższej okazji. Prowadź krótkie konto. Brak obsługi błędów prowadzi do:

•  Kruchy kod. Kod wypełniony ekscytującymi, trudnymi do znalezienia błędami.
•  Niebezpieczny kod. Crackerzy często wykorzystują słabą obsługę błędów, aby włamać się do systemów oprogramowania.
•  Słaba struktura. Jeśli w twoim kodzie występują błędy, z którymi żmudne jest ciągłe radzenie sobie z nimi, prawdopodobnie masz słaby interfejs. Wyraź to tak, aby błędy były mniej inwazyjne, a ich obsługa mniej uciążliwa.

Tak jak powinieneś sprawdzić wszystkie potencjalne błędy w swoim kodzie, musisz ujawnić wszystkie potencjalnie błędne warunki w swoich interfejsach. Nie ukrywaj ich, udając, że Twoje usługi zawsze będą działać. Dlaczego nie sprawdzamy błędów? Istnieje wiele powszechnych wymówek. Z którym z nich się zgadzasz? Jak byś przeciwdziałał każdemu z nich?

•  Obsługa błędów zaśmieca przepływ kodu, utrudniając jego czytanie i utrudniając dostrzeżenie "normalnego" przebiegu wykonywania.
•  To dodatkowa praca, a zbliża się termin.
•  Wiem, że to wywołanie funkcji nigdy nie zwróci błędu (printf zawsze działa, malloc zawsze zwraca nową pamięć - jeśli się nie powiedzie, mamy większe problemy…).
•  To tylko program zabawkowy i nie musi być napisany na poziomie godnym produkcji.

Nie tylko ucz się języka, zrozum jego kulturę

W swojej przełomowej książce The Pragmatic Programmer Andy Hunt i Dave Thomas zachęcają nas do nauki każdego roku nowego języka programowania. Starałem się żyć z ich rad i przez lata miałem doświadczenie w programowaniu w wielu językach. Najważniejszą lekcją z moich przygód z poliglotą jest to, że do nauki języka potrzeba czegoś więcej niż tylko nauki składni: musisz zrozumieć jego kulturę. Możesz pisać Fortran w dowolnym języku, ale aby naprawdę nauczyć się języka, musisz go zaakceptować. Nie usprawiedliwiaj się, jeśli Twój kod C# jest długą metodą Main z przeważnie statycznymi metodami pomocniczymi, ale dowiedz się, dlaczego klasy mają sens. Nie wahaj się, jeśli masz trudności ze zrozumieniem wyrażeń lambda używanych w językach funkcjonalnych - zmuś się do ich użycia. Gdy nauczysz się podstaw nowego języka, będziesz zaskoczony, jak zaczniesz używać języków, które już znasz w nowy sposób. Nauczyłem się efektywnie korzystać z delegatów w C# od programowania Rubiego; uwolnienie pełnego potencjału generyków .NET dało mi pomysły na to, jak uczynić generyki Java bardziej użytecznymi; a LINQ sprawiło, że nauka Scala była bardzo prosta. Możesz także lepiej zrozumieć wzorce projektowe, poruszając się między różnymi językami. Programiści C stwierdzają, że C# i Java utowarowiły wzorzec iteratora. W Ruby i innych dynamicznych językach nadal możesz używać gościa, ale twoja implementacja nie będzie wyglądać jak przykład z książki Gang of Four. Niektórzy mogą twierdzić, że Finnegans Wake jest nieczytelny, podczas gdy inni chwalą go za stylistyczne piękno. Aby książka była mniej zniechęcająca, dostępne są tłumaczenia na jeden język. Jak na ironię, pierwszy z nich był po francusku. Kod jest pod wieloma względami podobny. Jeśli napiszesz kod Wakese z odrobiną Pythona, trochę Javy i odrobiną Erlanga, twoje projekty będą bałaganem. Jeśli zamiast tego odkryjesz nowe języki, aby poszerzyć swój umysł i uzyskać nowe pomysły na rozwiązywanie problemów na różne sposoby, przekonasz się, że kod, który piszesz w swoim zaufanym starym języku, staje się piękniejszy z każdym nowym językiem, którego się nauczyłeś.

Nie przybijaj swojego programu do pozycji pionowej

Kiedyś napisałem fałszywy quiz C++, w którym satyrycznie zasugerowałem następującą strategię obsługi wyjątków:

Dzięki mnóstwu konstrukcji try...catch w całej naszej bazie kodu, czasami jesteśmy w stanie zapobiec przerywaniu pracy naszych aplikacji. O stanie wynikowym myślimy jako o "przybijaniu trupa w pozycji wyprostowanej."

Pomimo mojej lekkomyślności, właściwie podsumowywałam lekcję, którą otrzymałam na kolanach samej Dame Bitter Experience. Była to podstawowa klasa aplikacji w naszej własnej, domowej bibliotece C++. Przez lata cierpiał na szturchanie palców wielu programistów: nikt nie miał czystych rąk. Zawierał kod do obsługi wszystkich wyjątków, które uniknęły od wszystkiego innego. Idąc za nami od Yossariana w Paragrafie 22, zdecydowaliśmy, a raczej poczuliśmy (zdecydowaliśmy więcej przemyśleć niż zagłębić się w konstrukcję tego potwora), że instancja tej klasy powinna żyć wiecznie lub umrzeć podczas próby. W tym celu połączyliśmy wiele programów obsługi wyjątków. Pomieszaliśmy ustrukturyzowaną obsługę wyjątków systemu Windows z rodzajem natywnym (pamiętasz __try...__except in C++? Ja też nie). Gdy coś niespodziewanie wypadło, próbowaliśmy je ponownie wywołać, mocniej naciskając parametry. Patrząc wstecz, lubię myśleć, że pisząc wewnętrzną próbę... tresura catch w klauzuli catch innej, wkradł się jakiś rodzaj świadomości, że mogłam przypadkowo zjechać z autostrady dobrej praktyki w aromatyczną ale niezdrowy pas obłędu. Jest to jednak prawdopodobnie mądrość retrospektywna. Nie trzeba dodawać, że za każdym razem, gdy coś poszło nie tak w aplikacjach opartych na tej klasie, znikały one jak ofiary mafii w dokach, nie pozostawiając po sobie żadnego użytecznego śladu bąbelków wskazujących, co się u diabła stało, pomimo procedur zrzutu, które rzekomo miały na celu rejestrację katastrofy . W końcu - na długo - podsumowaliśmy to, co zrobiliśmy i poczuliśmy wstyd. Zamieniliśmy cały bałagan na minimalny i solidny mechanizm raportowania. Ale to było wiele wypadków. Nie zawracałbym ci głowy tym - bo z pewnością nikt nigdy nie mógłby być tak głupi jak my - gdyby nie sprzeczka online, jaką ostatnio miałam z facetem, którego tytuł akademicki deklarował, że powinien wiedzieć lepiej. Dyskutowaliśmy o kodzie Java w zdalnej transakcji. Argumentował, że jeśli kod się nie powiedzie, powinien przechwycić i zablokować wyjątek in situ. ("A potem co z tym zrobić?" - zapytałem. "Ugotować na kolację?"). Zacytował zasadę projektantów interfejsu użytkownika: NIGDY NIE POZWÓL UŻYTKOWNIKOWI ZOBACZYĆ RAPORTU WYJĄTKOWEGO, raczej tak, jakby to załatwiło sprawę, co z tym jest. w czapkach i tak dalej. Zastanawiam się, czy był odpowiedzialny za kod w jednym z tych niebieskich ekranów bankomatów, których zdjęcia zdobią słabsze blogi, i doznał trwałej traumy. W każdym razie, jeśli go spotkasz, kiwnij głową, uśmiechnij się i nie zwracaj na to uwagi, gdy chowasz się do drzwi.

Nie polegaj na "Magia się tu dzieje"

Jeśli spojrzysz na jakąkolwiek aktywność, proces lub dyscyplinę z odpowiedniej odległości, wygląda to na proste. Menedżerowie bez doświadczenia w rozwoju uważają, że to, co robią programiści, jest proste, a programiści bez doświadczenia w zarządzaniu myślą tak samo, jak menedżerowie. Programowanie to coś, co niektórzy ludzie robią - czasami. A najtrudniejsza część - myślenie - jest najmniej widoczna i najmniej doceniana przez niewtajemniczonych. Przez dziesięciolecia podejmowano wiele prób usunięcia potrzeby tego umiejętnego myślenia. Jednym z najwcześniejszych i najbardziej pamiętnych jest wysiłek Grace Hopper, aby uczynić języki programowania mniej tajemniczymi - co, jak przewidywały niektóre konta, usunęłoby potrzebę wyspecjalizowanych programistów. Wynik (COBOL) przyczynił się do dochodów wielu specjalistów programistów na przestrzeni kolejnych dziesięcioleci. Trwała wizja, że tworzenie oprogramowania można uprościć przez usunięcie programowania, jest dla programisty, który rozumie, co się z tym wiąże, oczywiście naiwna. Ale proces umysłowy, który prowadzi do tego błędu, jest częścią ludzkiej natury, a programiści są tak samo skłonni do tego, jak wszyscy inni. W każdym projekcie jest prawdopodobnie wiele rzeczy, w które indywidualny programista nie jest aktywnie zaangażowany: pozyskiwanie wymagań od użytkowników, zatwierdzanie budżetów, konfigurowanie serwera kompilacji, wdrażanie aplikacji do środowisk QA i produkcyjnych, migracja firmy z stare procesy lub programy itp. Kiedy nie jesteś aktywnie zaangażowany w coś, istnieje nieświadoma tendencja do zakładania, że są one proste i powstają "za pomocą magii". Podczas gdy magia nadal się dzieje, wszystko jest w porządku. Ale kiedy - zwykle jest to "kiedy", a nie "jeśli" - magia się kończy, projekt ma kłopoty. Widziałem, jak projekty tracą tygodnie czasu programisty, ponieważ nikt nie rozumiał, w jaki sposób polegają na "właściwej" wersji ładowanej biblioteki DLL. Kiedy coś zaczęło sporadycznie zawodzić, członkowie zespołu rozglądali się wszędzie, zanim ktoś zauważył, że ładowana jest "niewłaściwa" wersja biblioteki DLL. Inny dział działał sprawnie - projekty dostarczane na czas, brak sesji debugowania w późnych godzinach wieczornych, żadnych napraw awaryjnych. Tak gładko, że kierownictwo wyższego szczebla zdecydowało, że sprawy "działają same" i może obejść się bez kierownika projektu. W ciągu sześciu miesięcy projekty w dziale wyglądały tak samo jak reszta organizacji - spóźnione, z błędami i ciągle łatane. Nie musisz rozumieć całej magii, która sprawia, że Twój projekt działa, ale zrozumienie niektórych z nich nie zaszkodzi - lub docenienie kogoś, kto rozumie fragmenty, których Ty nie znasz. Co najważniejsze, upewnij się, że gdy magia się zatrzyma, można ją uruchomić ponownie.

Nie powtarzaj się

Ze wszystkich zasad programowania, Don7prime;t Repeat Yourself (DRY) jest prawdopodobnie jedną z najbardziej podstawowych. Zasada ta została sformułowana przez Andy′ego Hunta i Dave′a Thomasa w The Pragmatic Programmer i stanowi podstawę wielu innych znanych najlepszych praktyk tworzenia oprogramowania i wzorców projektowych. Programista, który nauczy się rozpoznawać duplikację i rozumie, jak ją wyeliminować poprzez odpowiednią praktykę i odpowiednią abstrakcję, może stworzyć znacznie czystszy kod niż ten, który nieustannie infekuje aplikację niepotrzebnymi powtórzeniami.

Powielanie to marnotrawstwo

Każda linia kodu, która trafia do aplikacji, musi zostać utrzymana i jest potencjalnym źródłem przyszłych błędów. Duplikacja niepotrzebnie powiększa bazę kodu, co daje więcej okazji do błędów i dodaje przypadkowej złożoności do systemu. Nadmiar, który duplikacja dodaje do systemu, utrudnia również programistom pracującym z systemem pełne zrozumienie całości lub mieć pewność, że zmiany dokonywane w jednej lokalizacji nie muszą być wprowadzane również w innych miejscach, które powielają logikę, nad którą pracują. DRY wymaga, aby "każda wiedza miała jedną, jednoznaczną, autorytatywną reprezentację w systemie".

Powtarzanie w procesach wezwania do automatyzacji

Wiele procesów w tworzeniu oprogramowania jest powtarzalnych i łatwo zautomatyzowanych. W tych kontekstach oraz w kodzie źródłowym aplikacji obowiązuje zasada DRY. Testowanie ręczne jest powolne, podatne na błędy i trudne do powtórzenia, dlatego w miarę możliwości należy używać zestawów testów automatycznych. Integracja oprogramowania może być czasochłonna i podatna na błędy, jeśli jest wykonywana ręcznie, dlatego proces kompilacji powinien być uruchamiany tak często, jak to możliwe, najlepiej przy każdym zameldowaniu. Wszędzie tam, gdzie istnieją bolesne, ręczne procesy, które można zautomatyzować, należy je zautomatyzować i ustandaryzować. Celem jest upewnienie się, że istnieje tylko jeden sposób wykonania zadania i jest to jak najbardziej bezbolesne.

Powtarzanie w logicznych wywołaniach abstrakcji

Powtarzanie w logice może przybierać różne formy. Logika kopiowania i wklejania if-then lub switchcase jest jedną z najłatwiejszych do wykrycia i poprawienia. Wiele wzorców projektowych ma wyraźny cel ograniczenia lub wyeliminowania powielania logiki w aplikacji. Jeśli obiekt zazwyczaj wymaga kilku rzeczy, zanim będzie mógł zostać użyty, można to osiągnąć za pomocą wzorca Fabryka abstrakcyjna lub Metoda fabryki. Jeśli obiekt ma wiele możliwych wariacji w swoim zachowaniu, zachowania te można wstrzykiwać za pomocą wzorca Strategia, a nie dużych struktur jeśli-to. W rzeczywistości samo formułowanie wzorców projektowych jest próbą ograniczenia powielania wysiłków niezbędnych do rozwiązywania typowych problemów i omawiania takich rozwiązań. Ponadto DRY można zastosować do struktur, takich jak schemat bazy danych, co skutkuje normalizacją.

Kwestia zasady

Inne zasady dotyczące oprogramowania są również związane z DRY. Zasada "Raz i tylko raz", która dotyczy tylko funkcjonalnego zachowania kodu, może być traktowana jako podzbiór DRY. Zasada Open/Closed, która mówi, że "elementy oprogramowania powinny być otwarte na rozszerzenie, ale zamknięte na modyfikację", działa w praktyce tylko wtedy, gdy przestrzega się DRY. Podobnie dobrze znana zasada pojedynczej odpowiedzialności, która wymaga, aby klasa miała "tylko jeden powód do zmiany", opiera się na DRY. W odniesieniu do struktury, logiki, procesu i funkcji zasada DRY dostarcza podstawowych wskazówek programistom i pomaga w tworzeniu prostszych, łatwiejszych w utrzymaniu aplikacji o wyższej jakości. Chociaż istnieją scenariusze, w których powtórzenie może być konieczne, aby spełnić wymagania dotyczące wydajności lub innych wymagań (np. denormalizacja danych w bazie danych), należy go używać tylko tam, gdzie bezpośrednio dotyczy rzeczywistego, a nie wyimaginowanego problemu.

Nie dotykaj tego kodu!

W pewnym momencie zdarzyło się to każdemu z nas. Twój kod został przeniesiony na serwer pomostowy w celu testowania systemu, a kierownik testów odpisuje, że napotkał problem. Twoja pierwsza reakcja to "Szybko, pozwól mi to naprawić - wiem, co jest nie tak". Jednak w szerszym sensie to, co jest złe, to to, że jako programista uważasz, że powinieneś mieć dostęp do serwera pomostowego. W większości internetowych środowisk programistycznych architekturę można podzielić w następujący sposób:

•  Lokalny rozwój i testy jednostkowe na maszynie programisty
•  Serwer programistyczny, na którym wykonywane są ręczne lub automatyczne testy integracyjne
•  Serwer pomostowy, na którym zespół QA i użytkownicy przeprowadzają testy akceptacyjne
•  Serwer produkcyjny

Tak, są tam posypane inne serwery i usługi, takie jak kontrola kodu źródłowego i biletowanie, ale masz pomysł. Korzystając z tego modelu, programista - nawet starszy programista - nigdy nie powinien mieć dostępu poza serwer programistyczny. Większość prac programistycznych odbywa się na lokalnym komputerze programisty przy użyciu jego ulubionej mieszanki IDE, maszyn wirtualnych i odpowiedniego posypania czarną magią na szczęście. Po sprawdzeniu w SCC, czy to automatycznie, czy ręcznie, należy przenieść go na serwer deweloperski, gdzie można go przetestować i w razie potrzeby poprawić, aby upewnić się, że wszystko działa razem. Jednak od tego momentu deweloper jest obserwatorem procesu. Kierownik pomostowy powinien spakować i przesłać kod na serwer pomostowy dla zespołu kontroli jakości. Tak jak programiści nie powinni mieć dostępu do niczego poza serwerem programistycznym, tak zespół QA i użytkownicy nie muszą dotykać niczego na serwerze programistycznym. Jeśli jest gotowy do testów akceptacyjnych, wytnij wydanie i zwiń; nie proś użytkownika, aby "po prostu spojrzał na coś naprawdę szybkiego" na serwerze deweloperskim. Pamiętaj, chyba że sam kodujesz projekt, inni ludzie mają tam kod i mogą nie być gotowi, aby użytkownik go zobaczył. Menedżer wydania jest jedyną osobą, która powinna mieć dostęp do obu. W żadnym wypadku - w żadnym wypadku - programista nie powinien mieć dostępu do serwera produkcyjnego. Jeśli wystąpi problem, personel pomocy technicznej powinien go naprawić lub poprosić o jego naprawę. Po zameldowaniu w SCC rzucą stamtąd łatkę. Niektóre z największych katastrof programistycznych, w których brałem udział, miały miejsce, ponieważ ktoś *kaszle*ja*kaszle* naruszył tę ostatnią zasadę. Jeśli jest zepsuty, produkcja nie jest miejscem, aby to naprawić.

Otocz zachowanie, a nie tylko stan

W teorii systemów ograniczanie jest jedną z najbardziej użytecznych konstrukcji w przypadku dużych i złożonych struktur systemowych. W branży oprogramowania dobrze rozumie się wartość hermetyzacji lub hermetyzacji. Zawieranie jest obsługiwane przez konstrukcje języka programowania, takie jak podprogramy i funkcje, moduły i pakiety, klasy i tak dalej. Moduły i pakiety odpowiadają na potrzeby enkapsulacji na większą skalę, podczas gdy klasy, podprogramy i funkcje odnoszą się do bardziej szczegółowych aspektów materii. Z biegiem lat odkryłem, że klasy wydają się być jedną z najtrudniejszych konstrukcji enkapsulacji dla programistów. Nie jest niczym niezwykłym znalezienie klasy z pojedynczą 3000-wierszową metodą główną lub klasą z metodami set i get tylko dla jej podstawowych atrybutów. Te przykłady pokazują, że zaangażowani programiści nie w pełni zrozumieli myślenie obiektowe, ponieważ nie wykorzystali mocy obiektów jako konstruktów modelowania. Dla programistów zaznajomionych z terminami POJO (Plain Old Java Object) i POCO (Plain Old C# Object lub Plain Old CLR Object), był to zamiar powrotu do podstaw OO jako paradygmatu modelowania - obiekty są proste i proste , ale nie głupi. Obiekt zawiera zarówno stan, jak i zachowanie, przy czym zachowanie jest zdefiniowane przez stan rzeczywisty. Rozważmy obiekt drzwi. Ma cztery stany: zamknięty, otwarty, zamknięty, otwarty. Zapewnia dwie operacje: otwórz i zamknij. W zależności od stanu operacje otwierania i zamykania będą zachowywać się inaczej. Ta nieodłączna właściwość obiektu sprawia, że proces projektowania jest koncepcyjnie prosty. Sprowadza się to do dwóch prostych zadań: przydziału i delegowania odpowiedzialności na różne obiekty, w tym protokoły interakcji międzyobiektowej. Jak to działa w praktyce najlepiej ilustruje przykład. Załóżmy, że mamy trzy klasy: Klient, Zamówienie i Przedmiot. Obiekt Klient jest naturalnym miejscem zastępczym limitu kredytowego i zasad walidacji kredytu. Obiekt Order wie o swoim skojarzonym Kliencie, a jego operacja addItem deleguje faktyczne sprawdzenie zdolności kredytowej, wywołując customer.validateCredit(item.price()). Jeśli warunek końcowy metody nie powiedzie się, można zgłosić wyjątek i przerwać zakup. Mniej doświadczeni programiści zorientowani obiektowo mogą zdecydować się na umieszczenie wszystkich reguł biznesowych w obiekcie bardzo często określanym jako OrderManager lub OrderService. W tych projektach Zamówienie, Klient i Pozycja są traktowane jako niewiele więcej niż tylko typy rekordów. Cała logika jest wydzielona z klas i połączona w jedną dużą, proceduralną metodę z wieloma wewnętrznymi konstrukcjami jeśli-to-inaczej. Te metody są łatwe do złamania i są prawie niemożliwe do utrzymania. Powód? Hermetyzacja jest zepsuta. Tak więc, na koniec, nie przerywaj enkapsulacji i używaj mocy swojego języka programowania, aby ją utrzymać.

Liczby zmiennoprzecinkowe nie są prawdziwe

Liczby zmiennoprzecinkowe nie są "liczbami rzeczywistymi" w sensie matematycznym, mimo że w niektórych językach programowania, takich jak Pascal i Fortran, nazywa się je rzeczywistymi. Liczby rzeczywiste mają nieskończoną precyzję i dlatego są ciągłe i bezstratne; Liczby zmiennoprzecinkowe mają ograniczoną precyzję, więc są skończone i przypominają "źle zachowujące się" liczby całkowite, ponieważ nie są równomiernie rozmieszczone w całym zakresie. Aby to zilustrować, przypisz 2147483647 (największą 32-bitową liczbę całkowitą ze znakiem) do 32-bitowej zmiennej zmiennoprzecinkowej (np. x) i wydrukuj ją. Zobaczysz 2147483648. Teraz wypisz x-64. Nadal 2147483648. Teraz wydrukuj x-65, a otrzymasz 2147483520! Czemu? Ponieważ odstęp między sąsiednimi liczbami zmiennoprzecinkowymi w tym zakresie wynosi 128, a operacje zmiennoprzecinkowe zaokrąglają do najbliższej liczby zmiennoprzecinkowej. Liczby zmiennoprzecinkowe IEEE to liczby o stałej precyzji oparte na notacji naukowej o podstawie dwa: 1.d1d2…dp1 × 2e, gdzie p jest precyzją (24 dla liczby zmiennoprzecinkowej, 53 dla double). Odstęp między dwiema kolejnymi liczbami wynosi 21-p+e, co można bezpiecznie przybliżyć przez ε|x|, gdzie ε jest epsilonem maszyny (21-p). Znajomość odstępów w sąsiedztwie liczby zmiennoprzecinkowej może pomóc w uniknięciu klasycznych błędów numerycznych. Na przykład, jeśli wykonujesz obliczenia iteracyjne, takie jak wyszukiwanie pierwiastka równania, nie ma sensu prosić o większą precyzję niż system liczbowy może dać w sąsiedztwie odpowiedzi. Upewnij się, że żądana tolerancja nie jest mniejsza niż odstęp, w przeciwnym razie będziesz zapętlać się na zawsze. Ponieważ liczby zmiennoprzecinkowe są przybliżeniami liczb rzeczywistych, nieuchronnie pojawia się mały błąd. Ten błąd, zwany zaokrągleniem, może prowadzić do zaskakujących wyników. Kiedy na przykład odejmujesz prawie równe liczby, najbardziej znaczące cyfry znoszą się wzajemnie, więc to, co było najmniej znaczącą cyfrą (tam, gdzie znajduje się błąd zaokrąglenia) jest promowane do najbardziej znaczącej pozycji w wyniku zmiennoprzecinkowym, zasadniczo zanieczyszczając wszelkie dalsze powiązane obliczenia (zjawisko znane jako smużenie). Musisz dokładnie przyjrzeć się swoim algorytmom, aby zapobiec tak katastrofalnemu anulowaniu. Aby to zilustrować, rozważ rozwiązanie równania x2 - 100000x + 1 = 0 za pomocą wzoru kwadratowego. Ponieważ argumenty w wyrażeniu -b + sqrt(b2 - 4) są prawie równe co do wielkości, możesz zamiast tego obliczyć pierwiastek r1 = -b - sqrt(b2 - 4), a następnie uzyskać r2 = 1/r1, ponieważ dla dowolne równanie kwadratowe, ax2 + bx + c = 0, pierwiastki spełniają r1r2 = c/a. Smużenie może wystąpić w jeszcze bardziej subtelny sposób. Załóżmy, że biblioteka naiwnie oblicza ex ze wzoru 1 + x + x2/2 + x3/3! + … Działa to dobrze dla dodatniego x, ale zastanów się, co się dzieje, gdy x jest dużą liczbą ujemną. Wyrażenia parzyste dają duże liczby dodatnie, a odejmowanie nieparzystych wielkości nie wpłynie nawet na wynik. Problem polega na tym, że zaokrąglenie w dużych, pozytywnych kategoriach ma znacznie większe znaczenie niż prawdziwa odpowiedź. Odpowiedź rozchodzi się w kierunku pozytywnej nieskończoności! Rozwiązanie tutaj jest również proste: dla ujemnego x oblicz ex = 1/e|x|. Powinno być oczywiste, że nie należy używać liczb zmiennoprzecinkowych w aplikacjach finansowych - do tego służą klasy dziesiętne w językach takich jak Python i C#. Liczby zmiennoprzecinkowe są przeznaczone do wydajnych obliczeń naukowych. Ale wydajność jest bezwartościowa bez dokładności, więc pamiętaj o źródle błędów zaokrąglania i odpowiednio koduj!

Spełnij swoje ambicje dzięki Open Source

Są całkiem spore szanse, że nie tworzysz oprogramowania w pracy, które spełnia Twoje najbardziej ambitne marzenia o tworzeniu oprogramowania. Być może tworzysz oprogramowanie dla ogromnej firmy ubezpieczeniowej, gdy wolisz pracować w Google, Apple, Microsoft lub we własnym startupie rozwijającym kolejną wielką rzecz. Nigdy nie dotrzesz tam, gdzie chcesz, tworząc oprogramowanie dla systemów, na których Ci nie zależy. Na szczęście istnieje odpowiedź na Twój problem: open source. Istnieją tysiące projektów open source, z których wiele jest bardzo aktywnych, które oferują dowolne doświadczenie w tworzeniu oprogramowania. Jeśli podoba Ci się pomysł tworzenia systemów operacyjnych, skorzystaj z pomocy przy jednym z kilkunastu projektów systemów operacyjnych. Jeśli chcesz pracować nad oprogramowaniem muzycznym, oprogramowaniem do animacji, kryptografią, robotyką, grami komputerowymi, ogromnymi grami online, telefonami komórkowymi lub czymkolwiek, prawie na pewno znajdziesz co najmniej jeden projekt open source poświęcony temu zainteresowaniu. Oczywiście nie ma darmowego obiadu. Musisz chcieć zrezygnować z wolnego czasu, ponieważ prawdopodobnie nie możesz pracować nad grą wideo o otwartym kodzie źródłowym w swojej codziennej pracy - nadal masz obowiązek wobec swojego pracodawcy. Ponadto bardzo niewiele osób zarabia na projektach open source - niektórzy robią, ale większość nie. Powinieneś być gotów poświęcić część swojego wolnego czasu (mniej czasu na granie w gry wideo i oglądanie telewizji cię nie zabije). Im ciężej pracujesz nad projektem open source, tym szybciej zrealizujesz swoje prawdziwe ambicje jako programista. Ważne jest również, aby wziąć pod uwagę umowę z pracownikiem - niektórzy pracodawcy mogą ograniczać wysokość wkładu, nawet w wolnym czasie. Ponadto należy uważać, aby nie naruszać praw własności intelektualnej dotyczących praw autorskich, patentów, znaków towarowych i tajemnic handlowych. Open source daje ogromne możliwości zmotywowanemu programiście. Najpierw zobaczysz, jak ktoś inny zaimplementowałby rozwiązanie, które Cię interesuje - możesz się wiele nauczyć, czytając kod źródłowy innych osób. Po drugie, możesz wnieść swój własny kod i pomysły do projektu - nie każdy genialny pomysł, jaki masz, zostanie zaakceptowany, ale niektóre mogą, i nauczysz się czegoś nowego po prostu pracując nad rozwiązaniami i dodając kod. Po trzecie, poznasz wspaniałych ludzi z taką samą pasją do rodzaju oprogramowania, jakie posiadasz - te przyjaźnie typu open source mogą trwać całe życie. Po czwarte, zakładając, że jesteś kompetentnym współpracownikiem, będziesz w stanie dodać rzeczywiste doświadczenie w technologii, która faktycznie Cię interesuje. Rozpoczęcie pracy z open source jest dość łatwe. Istnieje bogactwo dokumentacji na temat narzędzi, których będziesz potrzebować (zarządzanie kodem źródłowym, edytory, języki programowania, systemy kompilacji itp.). Najpierw znajdź projekt, nad którym chcesz pracować, i poznaj narzędzia, z których korzysta projekt. Dokumentacja dotycząca samych projektów będzie w większości przypadków lekka, ale może to mieć mniejsze znaczenie, ponieważ najlepszym sposobem na naukę jest samodzielne zbadanie kodu. Jeśli chcesz się zaangażować, możesz zaoferować pomoc z dokumentacją. Możesz też zacząć od zgłoszenia się na ochotnika do napisania kodu testowego. Chociaż może to nie brzmieć ekscytująco, prawda jest taka, że pisząc kod testowy dla oprogramowania innych osób uczysz się znacznie szybciej niż prawie każda inna aktywność w oprogramowaniu. Napisz kod testowy, naprawdę dobry kod testowy. Znajduj błędy, proponuj poprawki, nawiązuj znajomości, pracuj nad oprogramowaniem, które lubisz, i spełniaj swoje ambicje związane z rozwojem oprogramowania.

Złota zasada projektowania API

Projektowanie API jest trudne, szczególnie w dużych. Jeśli projektujesz API, które będzie miało setki lub tysiące użytkowników, musisz pomyśleć o tym, jak możesz to zmienić w przyszłości i czy Twoje zmiany mogą złamać kod klienta. Poza tym musisz pomyśleć o tym, jak użytkownicy Twojego API wpływają na Ciebie. Jeśli jedna z twoich klas API używa wewnętrznie jednej z własnych metod, musisz pamiętać, że użytkownik może podklasować twoją klasę i ją zastąpić, co może być katastrofalne. Nie będziesz w stanie zmienić tej metody, ponieważ niektórzy użytkownicy nadali jej inne znaczenie. Twoje przyszłe wewnętrzne wybory dotyczące implementacji są na łasce użytkowników. Twórcy API rozwiązują ten problem na różne sposoby, ale najprostszym sposobem jest zablokowanie API. Jeśli pracujesz w Javie, możesz ulec pokusie, aby większość twoich klas i metod była ostateczna. W języku C# możesz zapieczętować swoje klasy i metody. Bez względu na język, którego używasz, możesz pokusić się o zaprezentowanie swojego API za pomocą singletona lub użyć statycznych metod fabrycznych, aby chronić go przed ludźmi, którzy mogą nadpisać zachowanie i użyć twojego kodu w sposób, który może później ograniczyć twoje wybory. To wszystko wydaje się rozsądne, ale czy tak jest naprawdę? W ciągu ostatniej dekady stopniowo zdaliśmy sobie sprawę, że testowanie jednostkowe jest niezwykle ważną częścią praktyki, ale ta lekcja nie przeniknęła całkowicie branży. Dowody są wszędzie wokół nas. Weź dowolną niesprawdzoną klasę, która korzysta z interfejsu API innej firmy i spróbuj napisać dla niej testy jednostkowe. Przez większość czasu będziesz mieć kłopoty. Przekonasz się, że kod używający API przykleja się do niego jak klej. Nie ma możliwości podszycia się pod klasy API, aby można było wyczuć interakcje z nimi kodu lub dostarczyć wartości zwracane do testowania. Z czasem będzie się to poprawiać, ale tylko wtedy, gdy zaczniemy postrzegać testowanie jako rzeczywiste zastosowanie przypadku, gdy projektujemy API. Niestety, jest to trochę bardziej skomplikowane niż tylko testowanie naszego kodu. Tu właśnie wpasowuje się Złota Zasada Projektowania API: Nie wystarczy pisać testy dla tworzonego API; musisz napisać testy jednostkowe dla kodu, który używa twojego API. Stosując się do tej zasady, dowiadujesz się z pierwszej ręki, jakie przeszkody będą musieli pokonać Twoi użytkownicy, próbując samodzielnie przetestować swój kod. Nie ma jednego sposobu, aby ułatwić programistom testowanie kodu korzystającego z Twojego interfejsu API. statyczne, ostateczne i zapieczętowane nie są z natury złymi konstrukcjami. Czasami mogą się przydać. Ale ważne jest, aby być świadomym problemu z testowaniem i aby to zrobić, musisz sam tego doświadczyć. Kiedy już to zrobisz, możesz podejść do tego jak do każdego innego wyzwania projektowego.

Mit Guru

Każdy, kto wystarczająco długo pracował w oprogramowaniu, słyszał takie pytania: otrzymuję wyjątek XYZ. Czy wiesz, na czym polega problem? Osoby zadające pytanie rzadko zadają sobie trud dołączenia śladów stosu, dzienników błędów lub jakiegokolwiek kontekstu prowadzącego do problemu. Wydaje im się, że działasz na innej płaszczyźnie, że rozwiązania pojawiają się bez analizy opartej na dowodach. Myślą, że jesteś guru. Oczekujemy takich pytań od osób niezaznajomionych z oprogramowaniem; dla nich systemy mogą wydawać się niemal magiczne. Martwi mnie to, że widzę to w społeczności programistów. Podobne pytania pojawiają się przy projektowaniu programów, np. "Buduję zarządzanie zapasami. Czy powinienem używać optymistycznego blokowania?" Jak na ironię, ludzie zadający pytanie są często lepiej przygotowani do udzielenia na nie odpowiedzi niż adresat pytania. Osoby pytające przypuszczalnie znają kontekst, znają wymagania i potrafią przeczytać o zaletach i wadach różnych strategii. Jednak oczekują, że udzielisz inteligentnej odpowiedzi bez kontekstu. Oczekują magii. Czas, aby branża oprogramowania rozwiała ten mit guru. "Guru" są ludźmi. Stosują logikę i systematycznie analizują problemy, tak jak my wszyscy. Wykorzystują skróty myślowe i intuicję. Rozważ najlepszego programistę, jakiego kiedykolwiek spotkałeś: w pewnym momencie ta osoba wiedziała mniej o oprogramowaniu niż ty teraz. Jeśli ktoś wydaje się być guru, to z powodu lat poświęconych na naukę i doskonalenie procesów myślowych. "guru" to po prostu inteligentna osoba z niesłabnącą ciekawością. Oczywiście pozostaje ogromna rozbieżność w naturalnych uzdolnieniach. Wielu hakerów jest mądrzejszych, bardziej kompetentnych i bardziej produktywnych niż ja kiedykolwiek będę. Mimo to obalanie mitu guru ma pozytywny wpływ. Na przykład, pracując z kimś mądrzejszym ode mnie, na pewno wykonam całą robotę, aby zapewnić wystarczający kontekst, aby osoba mogła skutecznie zastosować swoje umiejętności. Usunięcie mitu guru oznacza również usunięcie postrzeganej bariery do poprawy. Zamiast magicznej bariery widzę kontinuum, wzdłuż którego mogę iść naprzód. Wreszcie, jedną z największych przeszkód w oprogramowaniu są inteligentni ludzie, którzy celowo propagują mit guru. Może to być zrobione z ego lub jako strategia zwiększania własnej wartości postrzeganej przez klienta lub pracodawcę. Jak na ironię, taka postawa może sprawić, że inteligentni ludzie będą mniej wartościowi, ponieważ nie przyczyniają się do rozwoju swoich rówieśników. Nie potrzebujemy guru. Potrzebujemy ekspertów, którzy chcą rozwijać innych ekspertów w swojej dziedzinie. Jest miejsce dla nas wszystkich.

Ciężka praca się nie opłaca

Jako programista przekonasz się, że ciężka praca często się nie opłaca. Możesz oszukać siebie i kilku współpracowników, aby uwierzyć, że dużo wnosisz do projektu, spędzając długie godziny w biurze. Ale prawda jest taka, że pracując mniej, możesz osiągnąć więcej, a czasem znacznie więcej. Jeśli starasz się być skoncentrowany i "produktywny" przez ponad 30 godzin tygodniowo, prawdopodobnie pracujesz zbyt ciężko. Powinieneś rozważyć zmniejszenie obciążenia pracą, aby stać się bardziej efektywnym i zrobić więcej. Stwierdzenie to może wydawać się sprzeczne z intuicją, a nawet kontrowersyjne, ale jest to bezpośrednia konsekwencja faktu, że programowanie i tworzenie oprogramowania jako całość wiąże się z ciągłym procesem uczenia się. Pracując nad projektem, lepiej zrozumiesz problematyczną dziedzinę i, miejmy nadzieję, znajdziesz skuteczniejsze sposoby osiągnięcia celu. Aby uniknąć marnowania pracy, musisz dać czas na obserwację efektów tego, co robisz, zastanowienie się nad rzeczami, które widzisz i odpowiednią zmianę swojego zachowania. Profesjonalne programowanie zwykle nie przypomina ciężkiego biegania przez kilka kilometrów, gdzie cel widać na końcu utwardzonej drogi. Większość projektów programistycznych przypomina raczej długi maraton na orientację. W ciemności. Tylko szkicowa mapa jako wskazówka. Jeśli po prostu wyruszysz w jednym kierunku, biegnąc tak szybko, jak potrafisz, możesz zaimponować niektórym, ale prawdopodobnie nie odniesiesz sukcesu. Musisz utrzymać stałe tempo i musisz dostosować kurs, gdy dowiesz się więcej o tym, gdzie jesteś i dokąd zmierzasz. Ponadto zawsze musisz dowiedzieć się więcej o tworzeniu oprogramowania w ogóle, aw szczególności o technikach programowania. Prawdopodobnie musisz czytać książki, chodzić na konferencje, komunikować się z innymi profesjonalistami, eksperymentować z nowymi technikami wdrożeniowymi i poznawać potężne narzędzia, które ułatwiają pracę. Jako zawodowy programista musisz być na bieżąco w swojej dziedzinie wiedzy - tak jak od neurochirurgów i pilotów oczekuje się, że będą na bieżąco informować o swoich dziedzinach. Musisz spędzać wieczory, weekendy i święta ucząc się; dlatego nie możesz spędzać wieczorów, weekendów i świąt pracując w nadgodzinach nad bieżącym projektem. Czy naprawdę oczekujesz, że neurochirurgowie będą wykonywać operacje 60 godzin tygodniowo, czy piloci będą latać 60 godzin tygodniowo? Oczywiście, że nie: przygotowanie i edukacja są istotną częścią ich zawodu. Skoncentruj się na projekcie, wnoś jak najwięcej, znajdując inteligentne rozwiązania, popraw swoje umiejętności, zastanów się nad tym, co robisz i dostosuj swoje zachowanie. Unikaj zawstydzania siebie i naszego zawodu, zachowując się jak chomik w klatce kręcąc kołem. Jako profesjonalny programista powinieneś wiedzieć, że koncentracja i "produktywność" 60 godzin tygodniowo nie jest rozsądną rzeczą. Zachowuj się jak profesjonalista: przygotuj, wykonaj, obserwuj, zastanów się i zmień.

Jak korzystać z narzędzia do śledzenia błędów

Bez względu na to, czy nazwiesz je błędami, defektami, czy nawet efektami ubocznymi projektowania, nie ma od nich ucieczki. Wiedza o tym, jak przesłać dobry raport o błędzie, a także czego w nim szukać, to kluczowe umiejętności, aby projekt mógł się dobrze rozwijać. Dobry raport o błędzie musi zawierać trzy rzeczy:

•  Jak jak najdokładniej odtworzyć błąd i jak często będzie się on pojawiał?
•  Co powinno było się wydarzyć, przynajmniej Twoim zdaniem
•  Co się faktycznie wydarzyło lub przynajmniej tyle informacji, ile zarejestrowałeś

Ilość i jakość informacji zgłoszonych w błędzie mówi tyle samo o zgłaszającym, co o błędzie. Wściekłe, zwięzłe błędy ("Ta funkcja jest do bani!") mówią programistom, że źle się bawiłeś, ale niewiele więcej. Błąd z mnóstwem kontekstu, który ułatwia powielanie, cieszy się szacunkiem wszystkich, nawet jeśli zatrzymuje wydanie. Błędy są jak rozmowa, z całą historią na oczach wszystkich. Nie obwiniaj innych ani nie zaprzeczaj samemu istnieniu błędu. Zamiast tego poproś o więcej informacji lub zastanów się, co mogłeś przegapić. Zmiana statusu błędu - np. Otwarty na Zamknięty - to publiczne oświadczenie o tym, co myślisz o błędzie. Poświęcenie czasu na wyjaśnienie, dlaczego uważasz, że błąd powinien zostać zamknięty, zaoszczędzi żmudne godziny spędzone później na uzasadnianiu go sfrustrowanym menedżerom i klientom. Zmiana priorytetu błędu jest podobnym publicznym oświadczeniem i tylko dlatego, że jest dla ciebie trywialna, nie oznacza, że nie powstrzymuje ona kogoś innego przed korzystaniem z produktu. Nie przeciążaj pól błędów do własnych celów. Dodanie "WAŻNE:" do pola tematu może ułatwić sortowanie wyników niektórych raportów, ale ostatecznie zostaną one skopiowane przez innych i nieuchronnie zostaną źle wpisane lub będą musiały zostać usunięte w celu wykorzystania w innym raporcie. Zamiast tego użyj nowej wartości lub nowego pola i udokumentuj, w jaki sposób pole ma być używane, aby inni ludzie nie musieli się powtarzać. Upewnij się, że wszyscy wiedzą, jak znaleźć błędy, nad którymi zespół ma pracować. Zwykle można to zrobić za pomocą zapytania publicznego o oczywistej nazwie. Upewnij się, że wszyscy używają tego samego zapytania i nie aktualizuj go bez wcześniejszego poinformowania zespołu, że zmieniasz to, nad czym wszyscy pracują. Na koniec pamiętaj, że błąd nie jest standardową jednostką pracy, tak samo jak linia kodu nie jest precyzyjnym pomiarem wysiłku.

Popraw kod, usuwając go

Mniej znaczy więcej. To dość banalna maksyma, ale czasami naprawdę jest prawdziwa. Jednym z ulepszeń, które wprowadziłem do naszej bazy kodu w ciągu ostatnich kilku tygodni, jest usunięcie jego fragmentów. Oprogramowanie napisaliśmy zgodnie z zasadami XP, w tym YAGNI (czyli nie będziesz tego potrzebować). Ludzka natura jest taka, jaka jest, nieuchronnie zawiedliśmy w kilku miejscach. Zauważyłem, że wykonanie niektórych zadań trwało zbyt długo - prostych zadań, które powinny być niemal natychmiastowe. To dlatego, że były nadmiernie wdrożone - ozdobione dodatkowymi dzwonkami i gwizdkami, które nie były wymagane, ale czy czas wydawał się dobrym pomysłem. Uprościłem więc kod, poprawiłem wydajność produktu i zmniejszyłem poziom globalnej entropii kodu, po prostu usuwając szkodliwe funkcje z bazy kodu. Pomocne, moje testy jednostkowe mówią mi, że nie zepsułem niczego innego podczas operacji. Proste i całkowicie satysfakcjonujące doświadczenie. Dlaczego więc niepotrzebny kod w ogóle tam trafił? Dlaczego jeden programista odczuł potrzebę napisania dodatkowego kodu i jak przeszedł on po przeglądzie lub procesie parowania? Prawie na pewno coś takiego:

•  To było trochę zabawne, dodatkowe rzeczy, a programista chciał to napisać. (Wskazówka: pisz kod, ponieważ dodaje wartości, a nie dlatego, że cię bawi.)
•  Ktoś pomyślał, że może być potrzebny w przyszłości, więc uznał, że najlepiej będzie go zakodować teraz. (Wskazówka: to nie jest YAGNI. Jeśli nie potrzebujesz tego w tej chwili, nie pisz tego teraz.)
•  Nie wydawało się to tak dużym "dodatkiem", więc łatwiej było go wdrożyć, niż wrócić do klienta, aby sprawdzić, czy jest to naprawdę potrzebne. (Wskazówka: napisanie i utrzymanie dodatkowego kodu zawsze zajmuje więcej czasu. A klient jest w rzeczywistości całkiem przystępny. Mały, dodatkowy fragment kodu zmienia się z czasem w dużą część pracy, która wymaga konserwacji.)
•  Programista wymyślił dodatkowe wymagania, które nie zostały ani udokumentowane, ani omówione, aby uzasadnić dodatkową funkcję. Wymóg był w rzeczywistości fałszywy. (Wskazówka: programiści nie ustalają wymagań systemowych; robi to klient.)

Nad czym teraz pracujesz? Czy to wszystko jest potrzebne?

Zainstaluj mnie

Twój program w najmniejszym stopniu mnie nie interesuje. Jestem otoczony problemami i mam listę rzeczy do zrobienia tak długo, jak moja ręka. Jedynym powodem, dla którego teraz jestem na twojej stronie, jest to, że usłyszałem nieprawdopodobną plotkę, że każdy z moich problemów zostanie wyeliminowany przez twoje oprogramowanie. Wybacz mi, jeśli będę sceptyczny. Jeśli badania śledzenia gałki ocznej są poprawne, przeczytałem już tytuł i szukam podkreślonego na niebiesko tekstu oznaczonego Pobierz teraz. Nawiasem mówiąc, gdybym dotarł na tę stronę z przeglądarką linuksową z brytyjskiego adresu IP, prawdopodobnie chciałbym wersję linuksową z europejskiego serwera lustrzanego, więc proszę nie pytaj. Zakładając, że okno dialogowe pliku otwiera się od razu, wysyłam rzecz do folderu pobierania i kontynuuję czytanie. Wszyscy stale przeprowadzamy analizę kosztów i korzyści wszystkiego, co robimy. Jeśli twój projekt spadnie poniżej mojego progu choćby na sekundę, porzucę go i przejdę do czegoś innego. Najlepsza jest natychmiastowa gratyfikacja. Pierwszą przeszkodą jest instalacja. Nie sądzisz, że to duży problem? Przejdź teraz do folderu pobierania i rozejrzyj się. Pełne plików .tar i .zip, prawda? Jaki procent z nich rozpakowałeś? Ile zainstalowałeś? Jeśli jesteś podobny do mnie, tylko jedna trzecia robi więcej niż wypełnianie dysku twardego. Może potrzebuję wygody na wyciągnięcie ręki, ale nie chcę, żebyś wchodził do mojego domu bez zaproszenia. Zanim napiszę install, chciałbym dokładnie wiedzieć, gdzie umieszczasz rzeczy. To mój komputer i lubię utrzymywać go w porządku, kiedy tylko mogę. Chcę też móc usunąć twój program, gdy tylko się nim rozczaruję. Jeśli podejrzewam, że to niemożliwe, w pierwszej kolejności go nie zainstaluję. Moja maszyna jest teraz stabilna i chcę, żeby tak pozostało. Jeśli twój program jest oparty na GUI, chcę zrobić coś prostego i zobaczyć wynik. Czarodzieje nie pomagają, bo robią rzeczy, których nie rozumiem. Są szanse, że chcę przeczytać plik lub go napisać. Nie chcę tworzyć projektów, importować katalogów ani podawać mojego adresu e-mail. Jeśli wszystko działa, przejdź do samouczka. Jeśli twoje oprogramowanie jest biblioteką, kontynuuję czytanie twojej strony internetowej w poszukiwaniu przewodnika szybkiego startu. Chcę odpowiednika "Hello world" w pięciolinijkowym nobrainer z dokładnie takim wyjściem, jakie opisuje Twoja strona internetowa. Żadnych dużych plików XML ani szablonów do wypełnienia, tylko jeden skrypt. Pamiętaj, pobrałem też framework twojego rywala. Wiesz, ten, który na forach zawsze twierdzi, że jest o wiele lepszy od twojego? Jeśli wszystko działa, przejdź do samouczka. Jest samouczek, prawda? Taki, który mówi do mnie językiem, który rozumiem? A jeśli samouczek wspomni o moim problemie, rozweselę się. Teraz, kiedy czytam o tym, co mogę zrobić, zaczyna się robić ciekawie, a nawet zabawnie. Odsuń się i popijaj herbatę - czy wspominałem, że jestem z Wielkiej Brytanii? - a ja pobawię się twoimi przykładami i nauczę się korzystać z twojego dzieła. Jeśli to rozwiąże mój problem, wyślę Ci e-mail z podziękowaniem. Wyślę Ci raporty o błędach, gdy się zawiesi, a także sugestie dotyczące funkcji. Powiem nawet wszystkim moim znajomym, że twoje oprogramowanie jest najlepsze, mimo że nigdy nie próbowałem oprogramowania twojego rywala. A wszystko dlatego, że tak zadbałeś o moje pierwsze nieśmiałe kroki.Jak mogłem kiedykolwiek w ciebie zwątpić?

Komunikacja międzyprocesowa wpływa na czas odpowiedzi aplikacji

Czas odpowiedzi ma kluczowe znaczenie dla użyteczności oprogramowania. Niewiele rzeczy jest tak frustrujące, jak oczekiwanie na odpowiedź jakiegoś systemu oprogramowania, zwłaszcza gdy nasza interakcja z oprogramowaniem obejmuje powtarzające się cykle bodźców i odpowiedzi. Czujemy, że oprogramowanie marnuje nasz czas i wpływa na naszą produktywność. Jednak przyczyny słabego czasu odpowiedzi są mniej doceniane, zwłaszcza w nowoczesnych aplikacjach. Znaczna część literatury dotyczącej zarządzania wydajnością nadal koncentruje się na strukturach danych i algorytmach, zagadnieniach, które w niektórych przypadkach mogą mieć znaczenie, ale znacznie rzadziej zdominują wydajność w nowoczesnych wielowarstwowych aplikacjach korporacyjnych. Kiedy wydajność jest problemem w takich aplikacjach, z mojego doświadczenia wynika, że badanie struktur danych i algorytmów nie jest właściwym miejscem do szukania ulepszeń. Czas odpowiedzi w największym stopniu zależy od liczby zdalnych komunikacji międzyprocesowych (IPC) prowadzonych w odpowiedzi na bodziec. Chociaż mogą istnieć inne lokalne wąskie gardła, zwykle dominuje liczba zdalnej komunikacji międzyprocesowej. Każda zdalna komunikacja międzyprocesowa przyczynia się do pewnego znaczącego opóźnienia w całkowitym czasie odpowiedzi, a te indywidualne wkłady sumują się, zwłaszcza gdy są ponoszone kolejno. Najlepszym przykładem jest ładowanie falowe w aplikacji przy użyciu mapowania obiektowo-relacyjnego. Ripple loading opisuje sekwencyjne wykonywanie wielu wywołań baz danych do wybrania danych potrzebnych do zbudowania grafu obiektów. Gdy klient bazy danych jest serwerem aplikacji warstwy pośredniej wyświetlającym stronę WWW, te wywołania bazy danych są zwykle wykonywane sekwencyjnie w jednym wątku. Ich indywidualne opóźnienia kumulują się, przyczyniając się do ogólnego czasu odpowiedzi. Nawet jeśli każde wywołanie bazy danych zajmuje tylko 10 milisekund, strona wymagająca 1000 wywołań (co nie jest rzadkością) będzie miała co najmniej 10-sekundowy czas odpowiedzi. Inne przykłady obejmują wywoływanie usług internetowych, żądania HTTP z przeglądarki internetowej, wywoływanie obiektów rozproszonych, wiadomości typu żądanie-odpowiedź oraz interakcja z siecią danych za pośrednictwem niestandardowych protokołów sieciowych. Im więcej zdalnych IPC będzie potrzebnych do odpowiedzi na bodziec, tym dłuższy będzie czas odpowiedzi. Istnieje kilka stosunkowo oczywistych i dobrze znanych strategii zmniejszania liczby zdalnych komunikacji międzyprocesowych na bodziec. Jedną ze strategii jest zastosowanie zasady oszczędności, optymalizacja interfejsu między procesami tak, aby wymieniać dokładnie odpowiednie dane do danego celu przy minimalnej ilości interakcji. Inną strategią jest zrównoleglenie komunikacji międzyprocesowej tam, gdzie to możliwe, tak aby ogólny czas odpowiedzi był kierowany głównie przez IPC o najdłuższych opóźnieniach. Trzecią strategią jest buforowanie wyników poprzednich IPC, tak aby można było uniknąć przyszłych IPC poprzez korzystanie z lokalnej pamięci podręcznej. Projektując aplikację, pamiętaj o liczbie komunikacji międzyprocesowej w odpowiedzi na każdy bodziec. Analizując aplikacje, które cierpią na słabą wydajność, często stwierdzam, że stosunek IPC do bodźca wynosi tysiące do jednego. Zmniejszenie tego współczynnika, czy to przez buforowanie, zrównoleglanie, czy inną technikę, będzie się opłacać znacznie więcej niż zmiana wyboru struktury danych lub ulepszenie algorytmu sortowania.

Utrzymuj kompilację w czystości

Czy kiedykolwiek spojrzałeś na listę ostrzeżeń kompilatora o długości eseju o złym kodowaniu i pomyślałem sobie: "Wiesz, naprawdę powinienem coś z tym zrobić… ale nie mam teraz czasu"? Z drugiej strony, czy kiedykolwiek spojrzałeś na samotne ostrzeżenie, które pojawiło się w kompilacji i po prostu je naprawiłeś? Kiedy zaczynam nowy projekt od zera, nie ma ostrzeżeń, bałaganu, problemów. Ale wraz ze wzrostem bazy kodu, jeśli nie zwracam uwagi, bałagan, ostrzeżenia i problemy mogą zacząć się nawarstwiać. Kiedy jest dużo hałasu, znacznie trudniej jest znaleźć ostrzeżenie, które naprawdę chcę przeczytać, wśród setek ostrzeżeń, na których mi nie zależy. Aby ostrzeżenia znów były przydatne, staram się stosować politykę zerowej tolerancji dla ostrzeżeń z kompilacji. Nawet jeśli ostrzeżenie nie jest ważne, radzę sobie z tym. Jeśli nie jest to krytyczne, ale nadal istotne, naprawiam to. Jeśli kompilator ostrzega o potencjalnym wyjątku wskaźnika zerowego, naprawiam przyczynę - nawet jeśli "wiem", że problem nigdy nie pojawi się w środowisku produkcyjnym. Jeśli wbudowana dokumentacja (Javadoc lub podobna) odnosi się do parametrów, które zostały usunięte lub przemianowane, czyszczę dokumentację. Jeśli jest to coś, na czym naprawdę mi nie zależy i to naprawdę nie ma znaczenia, pytam zespół, czy możemy zmienić naszą politykę ostrzegania. Na przykład uważam, że dokumentowanie parametrów i wartości zwracanej metody w wielu przypadkach nie dodaje żadnej wartości, więc nie powinno być ostrzeżeniem, jeśli ich brakuje. Lub aktualizacja do nowej wersji języka programowania może spowodować, że kod, który wcześniej był w porządku teraz emituje ostrzeżenia. Na przykład, gdy Java 5 wprowadziła generyki, cały stary kod, który nie określał parametru typu generycznego, dawał ostrzeżenie. Jest to rodzaj ostrzeżenia, o które nie chcę być dręczony (przynajmniej jeszcze nie). Posiadanie zestawu ostrzeżeń niezgodnych z rzeczywistością nikomu nie służy. Upewniając się, że kompilacja jest zawsze czysta, nie będę musiał decydować, że ostrzeżenie jest nieistotne za każdym razem, gdy je napotkam. Ignorowanie rzeczy to praca umysłowa, i muszę pozbyć się całej niepotrzebnej pracy umysłowej, jaką mogę. Posiadanie czystej konstrukcji ułatwia również przejęcie mojej pracy przez kogoś innego. Jeśli zostawię ostrzeżenia, ktoś inny będzie musiał przebrnąć przez to, co jest istotne, a co nie. Lub, co bardziej prawdopodobne, ta osoba po prostu zignoruje wszystkie ostrzeżenia, w tym te znaczące. Ostrzeżenia z twojej kompilacji są przydatne. Musisz tylko pozbyć się hałasu, aby zacząć je zauważać. Nie czekaj na wielkie sprzątanie. Gdy pojawi się coś, czego nie chcesz widzieć, zajmij się tym od razu. Należy naprawić źródło ostrzeżenia, pominąć ostrzeżenie lub naprawić zasady dotyczące ostrzeżeń w narzędziu. Utrzymanie kompilacji w czystości to nie tylko utrzymywanie jej wolnej od błędów kompilacji lub niepowodzeń testów: ostrzeżenia są również ważną i krytyczną częścią higieny kodu.

Dowiedz się, jak korzystać z narzędzi wiersza poleceń

Obecnie wiele narzędzi do tworzenia oprogramowania jest pakowanych w formie zintegrowanych środowisk programistycznych (IDE). Visual Studio Microsoftu i Eclipse o otwartym kodzie źródłowym to dwa popularne przykłady, choć istnieje wiele innych. Jest wiele rzeczy do polubienia w IDE. Są nie tylko łatwe w użyciu, ale także uwalniają programistę od myślenia o wielu drobiazgach związanych z procesem budowania. Łatwość obsługi ma jednak swoją wadę. Zazwyczaj, gdy narzędzie jest łatwe w użyciu, dzieje się tak dlatego, że podejmuje za Ciebie decyzje i robi wiele rzeczy automatycznie, za kulisami. Tak więc, jeśli IDE jest jedynym środowiskiem programistycznym, którego kiedykolwiek używasz, możesz nigdy w pełni nie zrozumieć, co faktycznie robią twoje narzędzia. Klikasz przycisk, pojawia się jakaś magia iw folderze projektu pojawia się plik wykonywalny. Pracując z narzędziami do kompilacji wiersza poleceń, dowiesz się znacznie więcej o tym, co robią narzędzia podczas kompilowania projektu. Pisanie własnych plików make pomoże Ci zrozumieć wszystkie kroki (kompilacja, asemblacja, linkowanie itp.), które składają się na tworzenie pliku wykonywalnego. Eksperymentowanie z wieloma opcjami wiersza poleceń dla tych narzędzi jest również cennym doświadczeniem edukacyjnym. Aby rozpocząć korzystanie z narzędzi do budowania wiersza poleceń, możesz użyć narzędzi wiersza poleceń typu open source, takich jak GCC, lub możesz użyć tych dostarczonych z zastrzeżonym środowiskiem IDE. W końcu dobrze zaprojektowane IDE to tylko graficzna nakładka na zestaw narzędzi wiersza poleceń Oprócz lepszego zrozumienia procesu kompilacji, istnieją pewne zadania, które można wykonać łatwiej lub wydajniej za pomocą narzędzi wiersza poleceń niż z IDE. Na przykład możliwości wyszukiwania i zamiany udostępniane przez narzędzia grep i sed są często bardziej zaawansowane niż te, które można znaleźć w środowiskach IDE. Narzędzia wiersza polecenia z natury obsługują skrypty, co pozwala na automatyzację zadań, takich jak tworzenie zaplanowanych codziennych kompilacji, tworzenie wielu wersji projektu i uruchamianie zestawów testów. W środowisku IDE ten rodzaj automatyzacji może być trudniejszy (jeśli nie niemożliwy), ponieważ opcje budowania są zwykle określane za pomocą okien dialogowych GUI, a proces budowania jest wywoływany kliknięciem myszy. Jeśli nigdy nie wyjdziesz poza środowisko IDE, możesz nawet nie zdawać sobie sprawy, że tego rodzaju zautomatyzowane zadania są możliwe. Ale poczekaj. Czy IDE nie istnieje po to, aby ułatwić programowanie i poprawić produktywność programisty? No tak. Przedstawiona tutaj sugestia nie polega na tym, że powinieneś przestać używać IDE. Sugeruje się, że powinieneś "popatrzyć pod maskę" i zrozum, co robi dla Ciebie Twoje IDE. Najlepszym sposobem na to jest nauczenie się korzystania z narzędzi wiersza poleceń. Następnie, gdy wrócisz do korzystania z IDE, będziesz miał znacznie lepsze zrozumienie tego, co robi dla Ciebie i jak możesz kontrolować proces kompilacji. Z drugiej strony, gdy opanujesz korzystanie z narzędzi wiersza polecenia i poznasz ich moc i elastyczność, może się okazać, że wolisz wiersz poleceń od środowiska IDE.

Poznaj więcej niż dwa języki programowania

Psychologia programowania: ludzie już od dawna wiedzą, że wiedza programistyczna jest bezpośrednio związana z liczbą różnych paradygmatów programowania, z którymi programista czuje się komfortowo - to znaczy nie, że po prostu wie lub wie trochę, ale że potrafi naprawdę programować. Każdy programista zaczyna od jednego języka programowania. Język ten ma dominujący wpływ na sposób, w jaki programista myśli o oprogramowaniu. Bez względu na to, ile lat doświadczenia programista zdobędzie używając tego języka, jeśli pozostanie przy tym języku, będzie znał tylko ten język. Programista jednojęzyczny jest ograniczony w myśleniu przez ten język. Programista, który uczy się drugiego języka, stanie przed wyzwaniem, zwłaszcza jeśli ten język ma inny model obliczeniowy niż pierwszy. C, Pascal, Fortran - wszystkie mają ten sam podstawowy model obliczeniowy. Przejście z Fortran na C wprowadza kilka, ale nie wiele wyzwań. Przejście z C lub Fortran na C++ lub Adę wprowadza fundamentalne wyzwania w zachowaniu programów. Przejście z C++ na Haskell to znacząca zmiana, a co za tym idzie nie lada wyzwanie. Przejście z C do Prologu to bardzo konkretne wyzwanie. Możemy wymienić szereg paradygmatów obliczeniowych: proceduralny, obiektowy, funkcjonalny, logiczny, przepływu danych itp. Poruszanie się wśród tych paradygmatów stwarza największe wyzwania. Dlaczego te wyzwania są dobre? Ma to związek ze sposobem, w jaki myślimy o implementacji algorytmów oraz idiomami i wzorcami implementacji, które mają zastosowanie. W szczególności krzyżowe zapłodnienie leży u podstaw wiedzy fachowej. Idiomy dotyczące rozwiązywania problemów, które mają zastosowanie w jednym języku, mogą nie być możliwe w innym języku. Próba przeniesienia idiomów z jednego języka na inny uczy nas o obu językach i rozwiązywanym problemie. Wzajemne nawożenie w użyciu języków programowania ma ogromne skutki. Być może najbardziej oczywistym jest coraz częstsze stosowanie deklaratywnych sposobów wyrażania w systemach zaimplementowanych w językach imperatywnych. Każdy, kto zna się na programowaniu funkcjonalnym, może z łatwością zastosować podejście deklaratywne, nawet gdy używanie języka takiego jak C. Stosowanie podejść deklaratywnych generalnie prowadzi do krótszych i bardziej zrozumiałych programów. Na przykład C++ z pewnością bierze to pod uwagę dzięki pełnemu wsparciu dla programowania generycznego, co prawie wymaga deklaratywnego sposobu wyrażania. Konsekwencją tego wszystkiego jest to, że każdy programista powinien być dobrze wyszkolony w programowaniu w co najmniej dwóch różnych paradygmatach, a najlepiej przynajmniej w pięciu wspomnianych powyżej. Programiści powinni zawsze być zainteresowani nauką nowych języków, najlepiej według nieznanego paradygmatu. Nawet jeśli ich dzienna praca zawsze używa tego samego języka programowania, nie należy lekceważyć zwiększonego wyrafinowania używania tego języka, gdy dana osoba może inspirować się innymi paradygmatami. Pracodawcy powinni wziąć to pod uwagę i pozostawić pracownikom w swoim budżecie szkoleniowym miejsce na naukę języków, które nie są obecnie używane, jako sposób na zwiększenie zaawansowania używanych języków. Choć to dopiero początek, tygodniowy kurs szkoleniowy nie wystarczy, aby nauczyć się nowego języka: generalnie potrzeba kilku dobrych miesięcy używania, nawet w niepełnym wymiarze godzin, aby uzyskać odpowiednią praktyczną znajomość języka. Ważnymi czynnikami są idiomy użycia, a nie tylko składnia i model obliczeniowy.

Poznaj swoje IDE

W latach 80. nasze środowiska programistyczne nie były niczym lepszym niż gloryfikowane edytory tekstu jeśli mieliśmy szczęście. Podświetlanie składni, które w dzisiejszych czasach uważamy za oczywiste, było luksusem, który z pewnością nie był dostępny dla wszystkich. Ładne drukarki, aby ładnie sformatować nasz kod, były zwykle zewnętrznymi narzędziami, które trzeba było uruchomić, aby poprawić nasze odstępy. Debugery były również oddzielnymi programami uruchamianymi w celu przechodzenia przez nasz kod, ale z wieloma tajemniczymi naciśnięciami klawiszy. W latach 90. firmy zaczęły dostrzegać potencjalny dochód, jaki mogą uzyskać dzięki wyposażaniu programistów w lepsze i bardziej przydatne narzędzia. Zintegrowane środowisko programistyczne (IDE) połączyło poprzednie funkcje edycji z kompilatorem, debuggerem, ładną drukarką i innymi narzędziami. W tym czasie popularne stały się również menu i mysz, co oznaczało, że programiści nie musieli już uczyć się zagadkowych kombinacji klawiszy, aby korzystać ze swoich edytorów. Mogli po prostu wybrać swoje polecenie z menu. W XXI wieku IDE stały się tak powszechne, że są rozdawane za darmo przez firmy, które chcą zdobyć udział w rynku w innych obszarach. Nowoczesne IDE jest wyposażone w niesamowitą gamę funkcji. Moją ulubioną jest automatyczna refaktoryzacja, w szczególności Extract Method, w której mogę wybrać i przekonwertować fragment kodu na metodę. Narzędzie do refaktoryzacji pobierze wszystkie parametry, które należy przekazać do metody, co sprawia, że bardzo łatwo jest modyfikować kod. Moje IDE wykryje nawet inne fragmenty kodu, które również mogłyby zostać zastąpione tą metodą i zapyta mnie, czy też chciałbym je zastąpić. Kolejną niesamowitą cechą nowoczesnych środowisk IDE jest możliwość egzekwowania reguł stylu w firmie. Na przykład w Javie niektórzy programiści zaczęli określać wszystkie parametry jako ostateczne (co moim zdaniem jest stratą czasu). Jednakże, ponieważ mają taką regułę stylu, wszystko, co musiałbym zrobić, aby ją zastosować, to ustawić ją w moim IDE: otrzymuję ostrzeżenie dla każdego niekońcowego parametru. Reguły stylów mogą być również używane do znajdowania prawdopodobnych błędów, takich jak porównywanie obiektów z autoboxami pod kątem równości referencji, np. używanie == na wartościach pierwotnych, które są automatycznie pakowane do obiektów referencyjnych. Niestety, nowoczesne IDE nie wymagają od nas wysiłku, aby nauczyć się z nich korzystać. Kiedy po raz pierwszy programowałem C w systemie Unix, musiałem poświęcić sporo czasu na naukę działania edytora vi ze względu na jego stromą krzywą uczenia się. Ten czas spędzony z góry opłacił się sowicie przez lata. Piszę nawet szkic tego artykułu za pomocą vi. Nowoczesne IDE mają bardzo stopniową krzywą uczenia się, co może skutkować tym, że nigdy nie wyjdziemy poza najbardziej podstawowe użycie narzędzia. Moim pierwszym krokiem w nauce IDE jest zapamiętanie skrótów klawiaturowych. Ponieważ moje palce są na klawiaturze, gdy piszę kod, naciśnięcie Ctrl + Shift + I, aby wstawić zmienną, zapobiega przerwaniu przepływu, podczas gdy przełączanie w celu nawigacji po menu za pomocą myszy przerywa go. Te przerwy prowadzą do niepotrzebnych zmian kontekstu, co sprawia, że jestem znacznie mniej produktywny, jeśli staram się robić wszystko w leniwy sposób. Ta sama zasada dotyczy również umiejętności klawiatury: naucz się pisać bezwzrokowo; nie pożałujesz czasu zainwestowanego z góry. Wreszcie, jako programiści, mamy sprawdzone narzędzia do strumieniowania Unix, które mogą nam pomóc manipulować naszym kodem. Na przykład, jeśli podczas przeglądu kodu zauważyłem, że programiści nazwali wiele klas tak samo, mogłem je bardzo łatwo znaleźć za pomocą narzędzi find, sed, sort, uniq i grep, w ten sposób:

find . -nazwa "*.java" | sed 's/.*\///' | sortuj | uniq -c | grep -v "^ *1" | sortuj -r

Spodziewamy się, że hydraulik, który przyjdzie do naszego domu, będzie mógł użyć swojej małej latarki. Poświęćmy trochę czasu na przestudiowanie, jak stać się bardziej efektywnym dzięki naszemu IDE.

Poznaj swoje następne zobowiązanie

Poklepałem trzech programistów po ich ramionach i zapytałem co oni robili. "Dokonuję refaktoryzacji tych metod" - odpowiedział pierwszy. "Dodaję kilka parametrów do tej akcji internetowej" - odpowiedział drugi. Trzeci odpowiedział: "Pracuję nad tą historyjką użytkownika". Mogłoby się wydawać, że dwóch pierwszych było pochłoniętych szczegółami swojej pracy, podczas gdy tylko trzeci widział szerszy obraz i miał lepszą ostrość. Jednak gdy zapytałem, kiedy i co popełnią, obraz zmienił się diametralnie. Pierwsze dwa były dość jasne, jakie pliki będą w to zaangażowane, i zostaną ukończone w ciągu około godziny. Trzeci programista odpowiedział: "Och, myślę, że będę gotowy w ciągu kilku dni. Prawdopodobnie dodam kilka klas i może w jakiś sposób zmienię te usługi." Dwóm pierwszym nie zabrakło wizji ogólnego celu. Wybrali zadania, które ich zdaniem prowadziły w produktywnym kierunku i mogły zostać ukończone w ciągu kilku godzin. Po zakończeniu tych zadań wybierali nową funkcję lub refaktoryzację do pracy. Cały napisany kod został więc zrobiony z jasnym celem i ograniczonym, osiągalnym celem. Trzeci programista nie był w stanie rozłożyć problemu i pracował nad wszystkimi aspektami jednocześnie. Nie miał pojęcia, czego to będzie wymagało, w zasadzie programowania spekulacyjnego, mając nadzieję, że dojdzie do takiego momentu, w którym będzie w stanie się zaangażować. Najprawdopodobniej kod napisany na początku tej długiej sesji był słabo dopasowany do rozwiązania, które wyszło na końcu. Co zrobiłoby dwóch pierwszych programistów, gdyby ich zadania trwały dłużej niż dwie godziny? Po uświadomieniu sobie, że wzięli na siebie zbyt wiele, najprawdopodobniej odrzuciliby swoje zmiany, określili mniejsze zadania i zaczęli od nowa. Kontynuowanie pracy oznaczałoby brak koncentracji i prowadziłoby do wprowadzania do repozytorium kodu spekulacyjnego. Zamiast tego zmiany zostałyby odrzucone, ale spostrzeżenia zachowane. Trzeci programista może wciąż zgadywać i desperacko próbować połączyć swoje zmiany w coś, co można by zatwierdzić. W końcu nie możesz odrzucić zmian w kodzie, które zrobiłeś - to byłaby zmarnowana praca, prawda? Niestety, nie wyrzucenie kodu prowadzi do nieco dziwnego kodu, który nie ma wyraźnego celu w wejściu do repozytorium. W pewnym momencie nawet programiści skupieni na commitach mogą nie znaleźć czegoś użytecznego, o którym myśleli, że można je ukończyć w dwie godziny. Następnie przechodzili bezpośrednio w tryb spekulacji, bawiąc się kodem i, oczywiście, odrzucając zmiany, gdy tylko jakiś wgląd sprowadziłby ich z powrotem na właściwe tory. Nawet te pozornie nieustrukturyzowane sesje hakerskie mają cel: poznanie kodu, aby móc zdefiniować zadanie, które stanowiłoby produktywny krok. Poznaj swoje następne zobowiązanie. Jeśli nie możesz dokończyć, wyrzuć zmiany, a następnie zdefiniuj nowe zadanie, w które wierzysz, korzystając ze zdobytych spostrzeżeń. W razie potrzeby przeprowadzaj eksperymenty spekulatywne, ale nie pozwól sobie wpaść w tryb spekulacji bez zauważenia. Nie wprowadzaj zgadywania do swojego repozytorium.

Duże, połączone ze sobą dane należą do bazy danych

Jeśli Twoja aplikacja ma obsługiwać duży, trwały, wzajemnie połączony zestaw elementów danych, nie wahaj się przechowywać go w relacyjnej bazie danych. W przeszłości RDBMS były drogimi, rzadkimi, złożonymi i nieporęcznymi bestiami. Tak już nie jest. W dzisiejszych czasach systemy RDBMS są łatwe do znalezienia - prawdopodobnie system, z którego korzystasz, ma już jeden lub dwa zainstalowane. Niektóre bardzo wydajne systemy RDBMS, takie jak MySQL i PostgreSQL, są dostępne jako oprogramowanie typu open source, więc koszt zakupu nie stanowi już problemu. Co więcej, tak zwane wbudowane systemy baz danych można podłączyć jako biblioteki bezpośrednio do aplikacji, nie wymagając prawie żadnej konfiguracji ani zarządzania - dwa godne uwagi systemy open source to SQLite i HSQLDB. Systemy te są niezwykle wydajne. Jeśli dane aplikacji są większe niż pamięć RAM systemu, zindeksowana tabela RDBMS wykona rzędy wielkości szybciej niż typ kolekcji map w bibliotece, co spowoduje zaśmiecenie stron pamięci wirtualnej. Nowoczesna oferta baz danych może łatwo rosnąć wraz z Twoimi potrzebami. Z należytą starannością można w razie potrzeby skalować wbudowaną bazę danych do większego systemu bazodanowego. Później możesz przełączyć się z bezpłatnej oferty open source na lepiej obsługiwany lub mocniejszy system własnościowy. Kiedy już opanujesz SQL, pisanie aplikacji zorientowanych na bazy danych jest przyjemnością. Po zapisaniu prawidłowo znormalizowanych danych w bazie danych, łatwo jest efektywnie wyodrębnić fakty za pomocą czytelnego zapytania SQL; nie ma potrzeby pisania skomplikowanego kodu. Podobnie pojedyncze polecenie SQL może wykonać złożone zmiany danych. W przypadku jednorazowych modyfikacji - powiedzmy, zmiany w sposobie organizowania trwałych danych - nie trzeba nawet pisać kodu: wystarczy uruchomić bezpośredni interfejs SQL bazy danych. Ten sam interfejs umożliwia również eksperymentowanie z zapytaniami, omijając cykl kompilacji i edycji zwykłego języka programowania. Kolejną zaletą oparcia kodu na RDBMS jest obsługa relacji między elementami danych. Możesz opisać ograniczenia spójności danych w sposób deklaratywny, unikając ryzyka nieaktualnych wskaźników, które otrzymasz, jeśli zapomnisz zaktualizować dane w przypadku brzegowym. Na przykład możesz określić, że jeśli użytkownik zostanie usunięty, wiadomości wysłane przez tego użytkownika również powinny zostać usunięte. W dowolnym momencie możesz również tworzyć wydajne połączenia między podmiotami przechowywanymi w bazie danych, po prostu tworząc indeks. Nie ma potrzeby przeprowadzania kosztownych i rozległych refaktoryzacji pól klasowych. Ponadto kodowanie wokół bazy danych umożliwia wielu aplikacjom bezpieczny dostęp do danych. Ułatwia to aktualizację aplikacji do równoczesnego użytku, a także kodowanie każdej części aplikacji przy użyciu najbardziej odpowiedniego języka i platformy. Na przykład, możesz napisać zaplecze XML aplikacji internetowej w Javie, kilka skryptów kontrolnych w Ruby i interfejs wizualizacji w Przetwarzaniu. Na koniec pamiętaj, że RDBMS będzie się bardzo pocić, aby zoptymalizować polecenia SQL, co pozwoli Ci skoncentrować się na funkcjonalności aplikacji, a nie na dostrajaniu algorytmów. Zaawansowane systemy baz danych będą nawet korzystać z wielordzeniowych procesorów za twoimi plecami. Wraz z poprawą technologii zwiększa się również wydajność Twojej aplikacji.

Uczyć się języków obcych

Programiści muszą się komunikować. Dużo. Są okresy w życiu programisty, kiedy wydaje się, że większość komunikacji odbywa się z komputerem, a dokładniej z programami uruchomionymi na tym komputerze. Ta komunikacja dotyczy wyrażania pomysłów w sposób czytelny dla maszyn. Pozostaje to ekscytującą perspektywą: programy to idee urzeczywistniane, praktycznie bez fizycznej zawartości. Programiści muszą biegle posługiwać się językiem maszyny, rzeczywistym lub wirtualnym, oraz abstrakcjami, które można powiązać z tym językiem za pomocą narzędzi programistycznych. Ważne jest, aby nauczyć się wielu różnych abstrakcji, w przeciwnym razie niektóre idee staną się niezwykle trudne do wyrażenia. Dobrzy programiści muszą być w stanie wyjść poza swoją codzienną rutynę, być świadomi innych języków, które są wyraziste do innych celów. Zawsze przychodzi czas, kiedy to się opłaca. Poza komunikacją z maszynami programiści muszą komunikować się ze swoimi rówieśnikami. Dzisiejsze duże projekty to bardziej społeczne przedsięwzięcia niż po prostu stosowana sztuka programowania. Ważne jest, aby zrozumieć i wyrazić więcej, niż potrafią to abstrakcje odczytywane maszynowo. Większość najlepszych programistów, jakich znam, biegle posługuje się również językiem ojczystym, a także innymi językami. Nie chodzi tylko o komunikację z innymi: dobre mówienie językiem prowadzi również do jasności myśli, która jest niezbędna przy abstrahowaniu problemu. I o to też chodzi w programowaniu. Poza komunikacją z maszyną, sobą i rówieśnikami, projekt ma wielu interesariuszy, większość z innym zapleczem technicznym lub bez wiedzy technicznej. Żyją w testowaniu, jakości i wdrażaniu; w marketingu i sprzedaży; są użytkownikami końcowymi w jakimś biurze (lub sklepie lub domu). Musisz je zrozumieć i ich obawy. Jest to prawie niemożliwe, jeśli nie możesz mówić ich językiem - językiem ich świata, ich domeny. Chociaż możesz pomyśleć, że rozmowa z nimi poszła dobrze, prawdopodobnie nie. Jeśli rozmawiasz z księgowymi, potrzebujesz podstawowej wiedzy z zakresu rachunkowości centrów kosztów, kapitału związanego, kapitału zaangażowanego i in. Jeśli rozmawiasz z marketingiem lub prawnikami, niektóre z ich żargonu i języka (a tym samym ich umysłów) powinny być ci znane. Wszystkie te języki specyficzne dla domeny muszą być opanowane przez kogoś w projekcie - najlepiej przez programistów. Programiści są ostatecznie odpowiedzialni za wprowadzanie pomysłów w życie za pomocą komputera. I oczywiście życie to coś więcej niż projekty oprogramowania. Jak zauważył Karol Wielki, znać inny język to mieć inną duszę. W przypadku kontaktów poza branżą oprogramowania docenisz znajomość języków obcych. Wiedzieć, kiedy słuchać, a nie mówić. Wiedzieć, że większość języków jest bez słów.

Naucz się szacować

Jako programista musisz być w stanie przedstawić swoim menedżerom, współpracownikom i użytkownikom szacunki dotyczące zadań, które musisz wykonać, aby mieli dość dokładne wyobrażenie o czasie, kosztach, technologii i innych zasobach potrzebnych do osiągnąć swoje cele. Aby móc dobrze szacować, ważne jest oczywiście poznanie kilku technik estymacji. Przede wszystkim jednak fundamentalne jest, aby dowiedzieć się, czym są szacunki i do czego należy ich używać - choć może się to wydawać dziwne, wielu programistów i menedżerów tak naprawdę tego nie wie. Poniższa wymiana między kierownikiem projektu a programistą nie jest nietypowa:

Project Manager: Czy możesz oszacować czas potrzebny na opracowanie funkcji xyz?
Programista: jeden miesiąc.
Project Manager: To zdecydowanie za długo! Mamy tylko tydzień.
Programista: potrzebuję co najmniej trzech.
Project Manager: Mogę dać najwyżej dwa.
Programista: Umowa!

Na koniec programista wymyśla "oszacowanie", które odpowiada temu, co jest akceptowalne dla menedżera. Ale ponieważ uważa się, że jest to oszacowanie programisty, menedżer będzie rozliczał programistę z tego. Aby zrozumieć, co jest nie tak w tej rozmowie, potrzebujemy trzech definicji - oszacowania, celu i zaangażowania:

o Szacunek jest przybliżonym obliczeniem lub oceną wartości, liczby, ilości lub zakresu czegoś. Definicja ta implikuje, że oszacowanie jest rzeczywistą miarą opartą na twardych danych i wcześniejszych doświadczeniach - nadzieje i życzenia muszą być ignorowane podczas jej obliczania. Definicja implikuje również, że będąc przybliżonym, oszacowanie nie może być precyzyjne, np. zadanie rozwojowe nie może być szacowane na 234,14 dni.
o Cel to określenie pożądanego celu biznesowego, np. "System musi obsługiwać co najmniej 400 jednoczesnych użytkowników".
o Zobowiązanie to obietnica dostarczenia określonej funkcjonalności na określonym poziomie jakości do określonej daty lub wydarzenia. Jednym z przykładów może być "Funkcja wyszukiwania będzie dostępna w następnej wersji produktu".
Szacunki, cele i zobowiązania są od siebie niezależne, ale cele i zobowiązania powinny być oparte na rzetelnych szacunkach. Jak zauważa Steve McConnell: "Głównym celem szacowania oprogramowania nie jest przewidywanie wyniku projektu; chodzi o ustalenie, czy cele projektu są wystarczająco realistyczne, aby umożliwić kontrolowanie projektu w celu ich osiągnięcia". Zatem celem szacowania jest właściwe zarządzanie i planowanie projektu jak to możliwe, umożliwiając zainteresowanym stronom projektu podjęcie zobowiązań w oparciu o realistyczne cele. To, o co menedżer w poprzedniej rozmowie tak naprawdę prosił programistę, to złożenie zobowiązania w oparciu o nieokreślony cel, który menedżer miał na myśli, a nie przedstawienie szacunków. Następnym razem, gdy zostaniesz zapytany aby przedstawić wycenę, upewnij się, że wszyscy zaangażowani wiedzą, o czym mówią, a Twoje projekty będą miały większe szanse powodzenia. Teraz nadszedł czas, aby nauczyć się kilku technik.…

Naucz się mówić "Witaj świecie"

Paul Lee, nazwa użytkownika leep, bardziej znany jako Hoppy, miał reputację lokalnego eksperta w kwestiach programowania. Potrzebowałem pomocy. Podszedłem do biurka Hoppy'ego i zapytałem, czy mógłby rzucić okiem na jakiś kod dla mnie.

"Jasne", powiedział Hoppy, "podnieś krzesło".
Uważałem, aby nie przewrócić pustych puszek po coli ułożonych w piramidzie za nim.
"Jaki kod?"
- W funkcji w pliku - powiedziałem.
"Więc spójrzmy na tę funkcję". Hoppy odsunął na bok kopię K&R i wysunął przede mną klawiaturę.
"Gdzie jest IDE?"
Najwyraźniej Hoppy nie miał uruchomionego IDE, tylko jakiś edytor, którego nie mogłem obsługiwać. Złapał klawiaturę. Kilka naciśnięć klawiszy później otworzyliśmy plik - był to dość duży plik - i patrzyliśmy na funkcję - była to całkiem duża funkcja. Przeszedł do bloku warunkowego, o który chciałem zapytać.
"Co właściwie zrobi ta klauzula, jeśli x jest ujemne?"
Zapytałam.
"Na pewno jest źle".
Cały ranek próbowałem znaleźć sposób na zmuszenie x do wartości ujemnych, ale duża funkcja w dużym pliku była częścią dużego projektu, a cykl ponownej kompilacji, a następnie ponownego przeprowadzania moich eksperymentów wykańczał mnie. Czy ekspert taki jak Hoppy nie mógłby po prostu udzielić mi odpowiedzi? Hoppy przyznał, że nie jest pewien. Ku mojemu zdziwieniu nie sięgnął po K&R. Zamiast tego skopiował blok kodu do nowego bufora edytora, ponownie go wcisnął, opakował w funkcję. Chwilę później zakodował główną funkcję, która zapętlała się w nieskończoność, prosząc użytkownika o wprowadzenie wartości, przekazując je do funkcji i wyświetlając wynik. Zapisał bufor jako nowy plik, tryit.c. Wszystko to mogłem zrobić dla siebie, choć może nie tak szybko. Ale jego następny krok był cudownie prosty i wówczas zupełnie obcy mojemu sposobowi pracy:

$ cc tryit.c && ./a.out

Patrz! Jego rzeczywisty program, wymyślony zaledwie kilka minut wcześniej, już działał. Wypróbowaliśmy kilka wartości i potwierdziliśmy moje podejrzenia (więc w czymś miałem rację!), a następnie sprawdził odpowiednią sekcję K&R. Podziękowałem Hoppy'emu i wyszedłem, ponownie uważając, aby nie zakłócić piramidy jego puszki po coli. Po powrocie do własnego biurka zamknąłem swoje IDE. Tak się przyzwyczaiłem do pracy nad dużym projektem w ramach dużego produktu, że zacząłem myśleć, że to jest to, co ja powinienem zrobić. Komputer ogólnego przeznaczenia również może wykonywać niewielkie zadania. Otworzyłem edytor tekstu i zacząłem pisać:

#include int main() { printf("Witaj, świecie\n"); return 0; }

Niech Twój projekt przemówi za siebie

Twój projekt prawdopodobnie posiada system kontroli wersji. Być może jest podłączony do serwera ciągłej integracji, który weryfikuje poprawność poprzez automatyczne testy. To wspaniale. Do ciągłej integracji możesz dołączyć narzędzia do statycznej analizy kodu dla serwera do zbierania metryk kodu. Te metryki dostarczają informacji zwrotnych na temat określonych aspektów kodu, a także ich ewolucji w czasie. Podczas instalowania metryk kodu zawsze pojawi się czerwona linia, której nie chcesz przekraczać. Załóżmy, że zaczynasz od 20% pokrycia testami i nigdy nie chcesz spaść poniżej 15%. Ciągła integracja pomaga śledzić wszystkie te liczby, ale nadal musisz regularnie sprawdzać. Wyobraź sobie, że możesz delegować to zadanie do samego projektu i polegać na nim, aby raportować, gdy sytuacja się pogorszy. Musisz nadać swojemu projektowi głos. Można to zrobić przez e-mail lub komunikator, informując programistów o ostatnim spadku lub poprawie liczby. Ale jeszcze bardziej efektywne jest wcielenie projektu w swoje biuro za pomocą urządzenia do ekstremalnego sprzężenia zwrotnego (XFD). Ideą XFD jest napędzanie fizycznego urządzenia, takiego jak lampa, przenośna fontanna, robot-zabawka, a nawet wyrzutnia rakiet USB, w oparciu o wyniki automatycznej analizy. Za każdym razem, gdy Twoje granice zostaną przekroczone, urządzenie zmienia swój stan. W przypadku lampy zaświeci się, jasna i oczywista. Nie możesz przegapić wiadomości, nawet jeśli spieszysz się za drzwiami, aby wrócić do domu. W zależności od typu urządzenia ekstremalnego sprzężenia zwrotnego możesz usłyszeć przerwę w kompilacji, zobaczyć czerwone sygnały ostrzegawcze w kodzie, a nawet poczuć zapach kodu. Urządzenia można replikować w różnych lokalizacjach, jeśli pracujesz w zespole rozproszonym. Możesz umieścić sygnalizację świetlną w biurze kierownika projektu, wskazującą ogólny stan projektu. Twój kierownik projektu to doceni. Niech Twoja kreatywność poprowadzi Cię w wyborze odpowiedniego urządzenia. Jeśli twoja kultura jest raczej geekowa, możesz poszukać sposobów na wyposażenie maskotki drużyny w zabawki sterowane radiowo. Jeśli chcesz uzyskać bardziej profesjonalny wygląd, zainwestuj w eleganckie designerskie lampy. Szukaj w Internecie więcej inspiracji. Wszystko z wtyczką zasilania lub pilot zdalnego sterowania może być używany jako urządzenie ekstremalnego sprzężenia zwrotnego. Urządzenie do ekstremalnego sprzężenia zwrotnego działa jak skrzynka głosowa Twojego projektu. Projekt znajduje się teraz fizycznie z programistami, skarżąc się lub chwaląc ich zgodnie z zasadami wybranymi przez zespół. Możesz dalej prowadzić tę personifikację, stosując oprogramowanie do syntezy mowy i parę głośników. Teraz Twój projekt mówi sam za siebie.

Linker nie jest magicznym programem

Przygnębiająco często (zdarzyło mi się to ponownie tuż przed tym, jak to napisałem), pogląd wielu programistów na temat procesu przechodzenia od kodu źródłowego do statycznie połączonego pliku wykonywalnego w skompilowanym języku jest następujący:

1. Edytuj kod źródłowy.
2. Skompiluj kod źródłowy do plików obiektowych.
3. Dzieje się coś magicznego.
4. Uruchom plik wykonywalny.

Krok 3 to oczywiście etap łączenia. Dlaczego miałbym powiedzieć coś tak oburzającego? Zajmuję się wsparciem technicznym od dziesięcioleci i wciąż mam następujące obawy:

1. Linker mówi, że def jest zdefiniowany więcej niż raz.
2. Linker mówi, że abc jest nierozwiązanym symbolem.
3. Dlaczego mój plik wykonywalny jest tak duży?

Następnie "Co mam teraz zrobić?" zazwyczaj z wymieszanymi zwrotami "wydaje się" i "jakoś" i aurą zupełnego zdumienia. To "wydaje się" i "jakoś" wskazują, że proces łączenia jest postrzegany jako proces magiczny, prawdopodobnie zrozumiały tylko dla czarodziejów i czarnoksiężników. Proces kompilacji nie wywołuje tego rodzaju fraz, co sugeruje, że programiści generalnie rozumieją, jak działają kompilatory, a przynajmniej co robią. Linker to głupi, zwyczajny, prosty program. Wszystko, co robi, to łączenie ze sobą sekcji kodu i danych plików obiektowych, łączenie odnośników do symboli z ich definicjami, wyciąganie nierozwiązanych symboli z biblioteki i zapisywanie pliku wykonywalnego. Otóż to. Żadnych zaklęć! Bez magii! Nuda w pisaniu linkera polega zazwyczaj na dekodowaniu i generowaniu zazwyczaj absurdalnie nadmiernie skomplikowanych formatów plików, ale to nie zmienia zasadniczej natury linkera. Załóżmy więc, że linker mówi, że def jest zdefiniowany więcej niż raz. Wiele języków programowania, takich jak C, C++ i D, ma zarówno deklaracje, jak i definicje. Deklaracje zwykle trafiają do plików nagłówkowych, takich jak:

zewn. wewn. iii;
który generuje zewnętrzne odniesienie do symbolu iii. Z drugiej strony, definicja odkłada pamięć dla symbolu, zwykle pojawia się w pliku implementacyjnym i wygląda tak:

int iii = 3;
Ile może być definicji dla każdego symbolu? Jak w filmie Highlander, może być tylko jeden. A co, jeśli definicja iii pojawia się w więcej niż jednym pliku implementacyjnym?

// Plik a.c
int iii = 3;
// Plik b.c
double iii(int x) { return 3.7; }
Linker będzie narzekał, że iii jest wielokrotnie definiowany. Nie tylko może być tylko jeden, musi być jeden. Jeśli iii pojawia się tylko jako deklaracja, ale nigdy jako definicja, linker będzie narzekał, że iii jest nierozwiązanym symbolem. Aby określić, dlaczego plik wykonywalny ma taki rozmiar, spójrz na plik mapy, który opcjonalnie generują linkery. Plik mapy to nic innego jak lista wszystkich symboli w pliku wykonywalnym, wraz z ich adresami. W ten sposób dowiesz się, jakie moduły zostały połączone z biblioteki i jakie są rozmiary każdego modułu. Teraz możesz zobaczyć, skąd pochodzi wzdęcie. Często zdarzają się moduły biblioteki, do których nie masz pojęcia, dlaczego zostały połączone. Aby to rozgryźć, tymczasowo usuń podejrzany moduł z biblioteki i podłącz ponownie. Wygenerowany błąd niezdefiniowanego symbolu wskaże, kto odwołuje się do tego modułu. Chociaż nie zawsze jest od razu oczywiste, dlaczego otrzymujesz konkretny komunikat linkera, w linkerach nie ma nic magicznego. Mechanika jest prosta; to szczegóły, które musisz wymyślić w każdym przypadku.

Trwałość rozwiązań tymczasowych

Dlaczego tworzymy rozwiązania tymczasowe? Zazwyczaj istnieje jakiś pilny problem do rozwiązania. Może to być wewnętrzne dla zespołu programistów, jakieś narzędzia, które wypełniają lukę w łańcuchu narzędzi. Może być zewnętrzny, widoczny dla użytkowników końcowych, taki jak obejście dotyczące brakującej funkcjonalności. W większości systemów i zespołów znajdziesz oprogramowanie, które jest nieco odseparowane od systemu, które uważane jest za szkic, który może być kiedyś zmieniony, które nie jest zgodne ze standardami i wytycznymi, które ukształtowały resztę kodu. Nieuchronnie usłyszysz, jak programiści narzekają na to. Powody ich powstania są liczne i zróżnicowane, ale klucz do sukcesu rozwiązania tymczasowego jest prosty: jest ono przydatne. Rozwiązania przejściowe nabierają jednak bezwładności (lub pędu, w zależności od Twojego punktu widzenia). Ponieważ są tam, ostatecznie użyteczne i powszechnie akceptowane, nie ma natychmiastowej potrzeby robienia czegokolwiek innego. Za każdym razem, gdy interesariusz musi zdecydować, jakie działanie wnosi największą wartość, wiele z nich znajdzie się wyżej niż właściwa integracja rozwiązania tymczasowego. Czemu? Ponieważ tam jest, działa i jest akceptowane. Jedynym widocznym minusem jest to, że nie przestrzega wybranych standardów i wytycznych - z wyjątkiem kilku rynków niszowych, nie jest to uważane za znaczącą siłę. Zatem rozwiązanie tymczasowe pozostaje na swoim miejscu. Na zawsze. A jeśli pojawią się problemy z tym tymczasowym rozwiązaniem, jest mało prawdopodobne, aby istniała możliwość aktualizacji, która dostosuje je do przyjętej jakości produkcji. Co robić? Szybka tymczasowa aktualizacja tego tymczasowego rozwiązania często spełnia swoje zadanie i najprawdopodobniej zostanie dobrze przyjęta. Wykazuje te same mocne strony, co początkowe rozwiązanie tymczasowe jest po prostu bardziej aktualne. Czy to problem? Odpowiedź zależy od Twojego projektu i osobistego zaangażowania w standardy kodu produkcyjnego. Gdy system zawiera zbyt wiele rozwiązań przejściowych, rośnie jego entropia lub wewnętrzna złożoność, a jego konserwowalność maleje. Jednak jest to prawdopodobnie niewłaściwe pytanie, które należy zadać jako pierwsze. Pamiętaj, że mówimy o rozwiązaniu. Może to nie być Twoje preferowane rozwiązanie - jest mało prawdopodobne, aby było to rozwiązanie preferowane przez nikogo - ale motywacja do przepracowania tego rozwiązania jest słaba. Co więc możemy zrobić, jeśli widzimy problem?

1. Przede wszystkim unikaj tworzenia tymczasowego rozwiązania.
2. Zmień siły, które wpływają na decyzję kierownika projektu.
3. Zostaw to tak, jak jest.

Przyjrzyjmy się bliżej tym opcjom:

1. W wielu przypadkach unikanie nie wchodzi w grę. Istnieje rzeczywisty problem do rozwiązania, a standardy okazały się zbyt restrykcyjne. Możesz poświęcić trochę energii, próbując zmienić standardy - honorowe, aczkolwiek żmudne przedsięwzięcie - a ta zmiana nie będzie skuteczna w odpowiednim momencie dla twojego problemu.
2. Siły są zakorzenione w kulturze projektu, która opiera się wolicjonalnej zmianie. Może odnieść sukces w bardzo małych projektach - zwłaszcza jeśli to tylko Ty - i po prostu posprzątasz bałagan bez wcześniejszego pytania. Może się to również udać, jeśli w projekcie panuje taki bałagan, że widocznie jest w martwym punkcie, a trochę czasu na uporządkowanie jest powszechnie akceptowane.
3. Status quo ma zastosowanie automatycznie, jeśli poprzednia opcja nie.

Stworzysz wiele rozwiązań; niektóre z nich będą tymczasowe, większość z nich przyda się. Najlepszym sposobem na przezwyciężenie tymczasowych rozwiązań jest uczynienie ich zbędnymi, aby zapewnić bardziej eleganckie i użyteczne rozwiązanie. Obyś otrzymał spokój, by zaakceptować to, czego nie możesz zmienić, odwagę, by zmienić to, co możesz, i mądrość, by poznać różnicę.

Spraw, aby interfejsy były łatwe w użyciu i trudne do nieprawidłowego użycia

Jednym z najczęstszych zadań w tworzeniu oprogramowania jest specyfikacja interfejsu. Interfejsy występują na najwyższym poziomie abstrakcji (interfejsy użytkownika), na najniższym (interfejsy funkcji) i na poziomach pośrednich (interfejsy klas, interfejsy bibliotek itp.). Niezależnie od tego, czy współpracujesz z użytkownikami końcowymi, aby określić sposób ich interakcji z systemem, współpracujesz z programistami w celu określenia API, czy deklarujesz funkcje prywatne dla klasy, projektowanie interfejsu jest ważną częścią Twojej pracy. Jeśli zrobisz to dobrze, Twoje interfejsy będą przyjemnością w użyciu i zwiększą produktywność innych. Jeśli zrobisz to słabo, Twoje interfejsy będą źródłem frustracji i błędów. Dobre interfejsy to:

Łatwy w użyciu poprawnie

Osoby korzystające z dobrze zaprojektowanego interfejsu prawie zawsze używają go poprawnie, ponieważ jest to ścieżka najmniejszego oporu. W graficznym interfejsie użytkownika prawie zawsze klikają odpowiednią ikonę, przycisk lub pozycję menu, ponieważ jest to oczywiste i łatwe do zrobienia. W API prawie zawsze przekazują poprawne parametry z poprawnymi wartościami, ponieważ to jest najbardziej naturalne. Dzięki interfejsom, które są łatwe w użyciu, wszystko po prostu działa.

Trudno używać niepoprawnie

Dobre interfejsy przewidują błędy, które mogą popełniać ludzie, i sprawiają, że są one trudne - najlepiej niemożliwe - do popełnienia. Na przykład GUI może wyłączyć lub usunąć polecenia, które nie mają sensu w bieżącym kontekście, lub interfejs API może wyeliminować problemy z kolejnością argumentów, zezwalając na przekazywanie parametrów w dowolnej kolejności.

Dobrym sposobem na prawidłowe zaprojektowanie łatwych w użyciu interfejsów jest przećwiczenie ich, zanim zaistnieją. Stwórz makiety GUI - być może na tablicy lub przy użyciu kart indeksowych na stole - i baw się nim, zanim zostanie utworzony kod bazowy. Napisz wywołania do interfejsu API przed zadeklarowaniem funkcji. Zapoznaj się z typowymi przypadkami użycia i określ, jak interfejs ma się zachowywać. Co chcesz mieć możliwość kliknięcia? Co chcesz umieć przekazać? Łatwe w obsłudze interfejsy wydają się naturalne, ponieważ pozwalają robić to, co chcesz. Bardziej prawdopodobne jest, że wymyślisz takie interfejsy, jeśli opracujesz je z punktu widzenia użytkownika. (Ta perspektywa jest jedną z mocnych stron programowania "test-first"). Uczynienie interfejsów trudnymi do użycia w niewłaściwy sposób wymaga dwóch rzeczy. Po pierwsze, musisz przewidzieć błędy, które mogą popełniać użytkownicy, i znaleźć sposoby ich zapobiegania. Po drugie, musisz obserwować, w jaki sposób interfejs jest niewłaściwie używany we wczesnej wersji i modyfikować interfejs - tak, zmodyfikuj interfejs! - aby zapobiec takim błędom. Najlepszym sposobem zapobiegania niewłaściwemu użyciu jest uniemożliwienie takiego użytkowania. Jeśli użytkownicy nadal chcą cofnąć nieodwołalną czynność, spróbuj ją odwołać. Jeśli nadal przekazują niewłaściwą wartość do interfejsu API, postaraj się zmodyfikować interfejs API, aby przyjmował wartości, które użytkownicy chcą przekazać. Przede wszystkim pamiętaj, że interfejsy istnieją dla wygody ich użytkowników, a nie ich implementatorów.

Spraw, aby niewidzialne było bardziej widoczne

Wiele aspektów niewidzialności jest słusznie chwalonych jako zasady oprogramowania, których należy przestrzegać. Nasza terminologia jest bogata w metafory niewidzialności - przejrzystość mechanizmu i ukrywanie informacji, żeby wymienić tylko dwa. Oprogramowanie i proces jego tworzenia może być, parafrazując Douglasa Adamsa, w większości niewidoczne:

•  Kod źródłowy nie ma wrodzonej obecności, wrodzonego zachowania i nie jest zgodny z prawami fizyki. Jest widoczny po załadowaniu go do edytora, ale zamknij edytor i znika. Pomyśl o tym zbyt długo i jak drzewo upadające bez nikogo, kto by to usłyszał, zaczynasz się zastanawiać, czy w ogóle istnieje.
•  Działająca aplikacja ma obecność i zachowanie, ale nie ujawnia nic z kodu źródłowego, z którego została zbudowana. Strona główna Google jest przyjemnie minimalna; wszystko, co się za nim dzieje, jest z pewnością znaczące.
•  Jeśli skończyłeś w 90% i bez końca próbujesz debugować ostatnie 10%, to znaczy, że nie jesteś gotowy w 90%, prawda? Naprawianie błędów nie powoduje postępów. Nie płacisz za debugowanie. Debugowanie to strata. Dobrze jest sprawić, by odpady były bardziej widoczne, aby zobaczyć, czym są, i zacząć myśleć o tym, żeby nie tworzyć ich w pierwszej kolejności.
•  Jeśli Twój projekt najwyraźniej jest na dobrej drodze, a tydzień później jest spóźniony o sześć miesięcy, masz problemy - z których największym prawdopodobnie nie jest to, że jest spóźniony o sześć miesięcy, ale pola sił niewidzialności są wystarczająco silne, aby ukryć sześć miesięcy spóźnienia! Brak widocznego postępu jest równoznaczny z brakiem postępu.

Niewidzialność może być niebezpieczna. Myślisz jaśniej, kiedy masz coś konkretnego, z czym możesz powiązać swoje myślenie. Lepiej zarządzasz rzeczami, gdy je widzisz i widzisz, jak ciągle się zmieniają:

•  Pisanie testów jednostkowych dostarcza dowodów na to, jak łatwo jednostka kodu jest w testach jednostkowych. Pomaga ujawnić obecność (lub brak) cech rozwojowych, które chciałbyś, aby wykazywał kod, takich jak niskie sprzężenie i wysoka spójność.
•  Uruchamianie testów jednostkowych dostarcza dowodów na zachowanie kodu. Pomaga ujawnić obecność (lub brak) cech środowiska wykonawczego, które aplikacja ma wykazywać, takich jak niezawodność i poprawność.
•  Korzystanie z tablic ogłoszeniowych i kart sprawia, że postęp jest widoczny i konkretny. Zadania mogą być postrzegane jako nierozpoczęte, w toku lub wykonane bez odwoływania się do ukrytego narzędzia do zarządzania projektami i bez konieczności ścigania programistów w celu uzyskania fikcyjnych raportów o stanie.
•  Wykonywanie przyrostowego rozwoju zwiększa widoczność postępu rozwoju (lub jego braku) poprzez zwiększenie częstotliwości dowodów rozwoju. Ukończenie oprogramowania do wydania odsłania rzeczywistość; szacunki nie.

Najlepiej tworzyć oprogramowanie z dużą ilością regularnych widocznych dowodów. Widoczność daje pewność, że postęp jest prawdziwy, a nie złudzenie, celowy i niezamierzony, powtarzalny i nieprzypadkowy.

Przekazywanie komunikatów prowadzi do lepszej skalowalności w systemach równoległych

Programiści są uczeni od samego początku swoich studiów nad obliczeniami, że współbieżność - a zwłaszcza równoległość, specjalny podzbiór współbieżności - jest trudna, że tylko najlepsi mogą mieć nadzieję, że uda im się to zrobić dobrze, a nawet im się to nie uda. Niezmiennie duży nacisk kładzie się na wątki, semafory, monitory i to, jak trudno jest uzyskać równoczesny dostęp do zmiennych, aby były bezpieczne wątkowo. To prawda, że istnieje wiele trudnych problemów, które mogą być bardzo trudne do rozwiązania. Ale co jest źródłem problemu? Pamięć współdzielona. Prawie wszystkie problemy ze współbieżnością, o których ludzie mówią, odnoszą się do korzystania ze współdzielonej pamięci mutowalnej: warunki wyścigu, impas, livelock itp. Odpowiedź wydaje się oczywista: albo zrezygnuj ze współbieżności, albo zrezygnuj z pamięci współdzielonej! Rezygnacja ze współbieżności prawie na pewno nie wchodzi w grę. Komputery mają coraz więcej rdzeni niemal co kwartał, więc wykorzystanie prawdziwego paralelizmu staje się coraz ważniejsze. Nie możemy już dłużej polegać na coraz szybszych taktowaniach procesora w celu poprawy wydajności aplikacji. Tylko dzięki wykorzystaniu równoległości wydajność aplikacji poprawi się. Oczywiście brak poprawy wydajności jest opcją, ale jest mało prawdopodobne, aby użytkownicy mogli ją zaakceptować. Czy możemy więc zrezygnować ze wspólnej pamięci? Zdecydowanie. Zamiast używać wątków i pamięci współdzielonej jako naszego modelu programowania, możemy użyć procesów i przekazywania komunikatów. Proces oznacza tutaj po prostu chroniony niezależny stan z wykonywanym kodem, niekoniecznie proces systemu operacyjnego. Języki takie jak Erlang (i wcześniej occam) pokazały, że procesy są bardzo skutecznym mechanizmem programowania systemów współbieżnych i równoległych. Takie systemy nie mają wszystkich obciążeń związanych z synchronizacją, które mają współdzieloną pamięć, systemy wielowątkowe. Co więcej, istnieje formalny model - Komunikacja Sekwencyjnych Procesów (CSP) - który można zastosować jako część inżynierii takich systemów. Możemy pójść dalej i wprowadzić systemy przepływu danych jako sposób obliczeniowy. W systemie przepływu danych nie ma jawnie zaprogramowanego przepływu sterowania. Zamiast tego tworzony jest ukierunkowany wykres operatorów połączonych ścieżkami danych, a następnie dane są wprowadzane do systemu. Ocena jest kontrolowana przez gotowość danych w systemie. Zdecydowanie żadnych problemów z synchronizacją. To powiedziawszy, języki takie jak C, C++, Java, Python i Groovy są głównymi językami tworzenia systemów, a wszystkie z nich są przedstawiane programistom jako języki do tworzenia systemów wielowątkowych z pamięcią współdzieloną. Więc co można zrobić? Odpowiedzią jest użycie - lub, jeśli nie istnieją, stworzenie - bibliotek i frameworków, które zapewniają modele procesów i przekazywanie komunikatów, unikając wszelkiego użycia współdzielonej pamięci mutowalnej. Podsumowując, nie programowanie z pamięcią współdzieloną, ale zamiast tego używanie przekazywania komunikatów, jest prawdopodobnie najbardziej udanym sposobem implementacji systemów wykorzystujących równoległość, która jest obecnie endemiczna w sprzęcie komputerowym. Być może o dziwo, chociaż procesy poprzedzają wątki jako jednostkę współbieżności, przyszłość wydaje się polegać na wykorzystaniu wątków do implementacji procesów.

Przesłanie do przyszłości

Może dlatego, że większość z nich to mądrzy ludzie, ale przez te wszystkie lata, kiedy uczyłem i pracowałem ramię w ramię z programistami, wydaje się, że większość z nich uważała, że skoro problemy, z którymi się borykali, były trudne, rozwiązania powinny być po prostu tak trudne dla wszystkich (może nawet dla nich samych kilka miesięcy po napisaniu kodu) do zrozumienia i utrzymania. Pamiętam jeden incydent z Joe, uczniem mojej klasy struktur danych, który musiał przyjść, aby pokazać mi, co napisał. "Betcha nie może zgadnąć, co robi!" zapiał. - Masz rację - zgodziłam się, nie poświęcając zbyt wiele czasu na jego przykład i zastanawiając się, jak przekazać ważną wiadomość. "Jestem pewien, że ciężko nad tym pracowałeś. Zastanawiam się jednak, czy nie zapomniałeś czegoś ważnego. Powiedz, Joe, czy nie masz młodszego brata? "Tak. Jasne! Phil! Jest w twojej klasie wprowadzającej. On też uczy się programować!" - oznajmił z dumą Joe. "To świetnie" - odpowiedziałem. "Zastanawiam się, czy mógł odczytać ten kod". "Nie ma mowy!" powiedział Joe. "To jest trudne!" - Przypuśćmy - zasugerowałem - że to był prawdziwy, działający kod i że za kilka lat Phil został zatrudniony do wykonania aktualizacji konserwacyjnej. Co dla niego zrobiłeś? Joe tylko patrzył na mnie, mrugając. - Wiemy, że Phil jest naprawdę bystry, prawda? Joe skinął głową. "I nienawidzę tego mówić, ale jestem też całkiem sprytny!" Joe uśmiechnął się. "Więc jeśli nie mogę łatwo zrozumieć, co tu zrobiłeś, a twój bardzo mądry młodszy brat prawdopodobnie będzie się nad tym zastanawiał, co to oznacza w tym, co napisałeś?" Joe patrzył na swój kod trochę inaczej, wydawało mi się. "Co powiesz na to", zasugerowałem moim najlepszym głosem "Jestem twoim przyjaznym mentorem", "Pomyśl o każdej linijce kodu, którą napiszesz, jako wiadomości dla kogoś w przyszłości - kogoś, kto może być twoim młodszym bratem. Udawaj, że wyjaśniasz tej mądrej osobie, jak rozwiązać ten trudny problem. "Czy to jest to, co chciałbyś sobie wyobrazić? Że inteligentny programista w przyszłości zobaczy Twój kod i powie: "Wow! To jest świetne! Doskonale rozumiem, co tu zostało zrobione i jestem zdumiony, jaki to elegancki - nie, czekaj - jaki to piękny kawałek kodu. Pokażę innym członkom mojego zespołu. To arcydzieło!" "Joe, czy myślisz, że potrafisz napisać kod, który rozwiąże ten trudny problem, ale będzie tak piękny, że będzie śpiewał? Tak, jak nawiedzająca melodia. Myślę, że każdy, kto wymyśli bardzo trudne rozwiązanie, które masz tutaj, mógłby również napisać coś pięknego. Hmm…zastanawiam się, czy powinnam zacząć oceniać piękno? Jak myślisz, Joe? Joe odebrał pracę i spojrzał na mnie z lekkim uśmiechem na twarzy. - Rozumiem, profesorze, idę uczynić świat lepszym dla Phila. Dziękuję.

Brakujące możliwości polimorfizmu

Polimorfizm jest jedną z wielkich idei, która jest fundamentalna dla OO. Słowo to, zaczerpnięte z języka greckiego, oznacza wiele (poly) form (morf). W kontekście programowania polimorfizm odnosi się do wielu form określonej klasy obiektów lub metody. Ale polimorfizm to nie tylko alternatywne implementacje. Używany ostrożnie, polimorfizm tworzy małe zlokalizowane konteksty wykonania, które pozwalają nam pracować bez potrzeby stosowania pełnych bloków jeśli-to-inaczej. Bycie w kontekście pozwala nam robić właściwe rzeczy bezpośrednio, podczas gdy przebywanie poza tym kontekstem zmusza nas do zrekonstruowania go tak, abyśmy mogli zrobić właściwą rzecz. Dzięki starannemu wykorzystaniu alternatywnych implementacji możemy uchwycić kontekst, który pomoże nam wyprodukować mniej kodu, który jest bardziej czytelny. Najlepiej jest to zademonstrować za pomocą kodu, takiego jak następujący (nierealistycznie) prosty koszyk:

public class ShoppingCart {
private ArrayList< Item > cart = new ArrayList();
public void add(Item item) { cart.add(item); }
public Item takeNext() { return cart.remove(0); }
public boolean isEmpty() { return cart.isEmpty(); }
}

Załóżmy, że nasz sklep internetowy oferuje przedmioty, które można pobrać i przedmioty, które należy wysłać. Zbudujmy kolejny obiekt obsługujący te operacje:

public class Shipping {
public boolean ship(Item item, SurfaceAddress address) { … }
public boolean ship(Item item, EMailAddress address { … }
}

Gdy klient zakończy kasę, musimy wysłać towar:

while (!cart.isEmpty()) {
shipping.ship(cart.takeNext(), ???);
}

??? parametr nie jest jakimś nowym wymyślnym operatorem elvisa; to pytanie, czy powinienem wysłać przedmiot e-mailem, czy ślimakiem. Kontekst potrzebny do odpowiedzi na to pytanie już nie istnieje. Mogliśmy uchwycić metodę wysyłki w wartości logicznej lub enum, a następnie użyć opcji jeśli-to-inaczej do uzupełnienia brakującego parametru. Innym rozwiązaniem byłoby stworzenie dwóch klas, które obie rozszerzają Item. Nazwijmy je DownloadableItem i SurfaceItem. Teraz napiszmy trochę kodu. Promuję Item jako interfejs, który obsługuje jedną metodę, statek. Aby wysłać zawartość koszyka, zadzwonimy do item.ship(shipper). Klasy DownloadableItem i SurfaceItem zaimplementują dostarczane:

public class DownloadableItem implements Item {
public boolean ship(Shipping shipper, Customer customer) {
shipper.ship(this, customer.getEmailAddress());
}
}
public class SurfaceItem implements Item {
public boolean ship(Shipping shipper, Customer customer) {
shipper.ship(this, customer.getSurfaceAddress());
}
}

W tym przykładzie odpowiedzialność za pracę z wysyłką przekazaliśmy na każdy przedmiot. Ponieważ każdy przedmiot wie, jak najlepiej go wysłać, ten układ pozwala nam się z nim uporać bez potrzeby "jeśli to jeszcze". Kod demonstruje również użycie dwóch wzorców, które często dobrze ze sobą współgrają: Command i Double Dispatch. Efektywne wykorzystanie tych wzorców opiera się na ostrożnym wykorzystaniu polimorfizmu. Kiedy tak się stanie, nastąpi zmniejszenie liczby bloków jeśli-to-inaczej w naszym kodzie. Chociaż istnieją przypadki, w których znacznie bardziej praktyczne jest użycie if-the- else zamiast polimorfizmu, częściej zdarza się, że bardziej polimorficzny styl kodowania da mniejszą, bardziej czytelną i mniej kruchą bazę kodu. Liczba utraconych szans to prosta liczba instrukcji "jeśli to inaczej" w naszym kodzie.

News of the Weird: Testerzy to Twoi przyjaciele

Niezależnie od tego, czy nazywają siebie Zapewnieniem Jakości, czy Kontrolą Jakości, wielu programistów nazywa ich Kłopotami. Z mojego doświadczenia wynika, że programiści często mają wrogie relacje z ludźmi, którzy testują ich oprogramowanie. "Są zbyt wybredni" i "Chcą, żeby wszystko było idealne" to częste narzekania. Brzmi znajomo? Nie wiem dlaczego, ale zawsze miałem inne spojrzenie na testerów. Może dlatego, że "testerem" w mojej pierwszej pracy była sekretarka firmy. Margaret była bardzo miłą kobietą, która prowadziła biuro i próbowała nauczyć kilku młodych programistów, jak zachowywać się profesjonalnie przed klientami. Miała również dar znajdowania każdego błędu, nawet najbardziej niejasnego, w ciągu zaledwie kilku chwil. Pracowałem wtedy nad programem napisanym przez księgowego, który myślał, że jest programistą. Nie trzeba dodawać, że miał poważne problemy. Kiedy myślałem, że mam wyprostowany kawałek, Margaret próbowała go użyć i najczęściej zawodził w jakiś nowy sposób po zaledwie kilku naciśnięciach klawiszy. Czasami było to frustrujące i zawstydzające, ale była tak miłą osobą, że nigdy nie pomyślałem, że mogę ją winić za to, że źle wyglądam. W końcu nadszedł dzień, w którym Margaret była w stanie czysto uruchomić program, wprowadzić fakturę, wydrukować ją i zamknąć. Byłem podekscytowany. Co więcej, gdy zainstalowaliśmy go na maszynie naszego klienta, wszystko działało. Nigdy nie widzieli żadnych problemów, ponieważ Margaret pomogła mi najpierw je znaleźć i naprawić. Dlatego mówię, że testerzy to Twoi przyjaciele. Możesz pomyśleć, że testerzy sprawiają, że wyglądasz źle, zgłaszając błahe problemy. Ale kiedy klienci są zachwyceni, ponieważ nie przejmowali się tymi wszystkimi "drobnymi rzeczami", które QC kazała ci naprawić, wyglądasz świetnie. Widzisz, co mam na myśli? Wyobraź sobie: testujesz narzędzie, które wykorzystuje "przełomowe algorytmy sztucznej inteligencji" do znajdowania i rozwiązywania problemów ze współbieżnością. Odpalasz go i natychmiast zauważasz, że na ekranie powitalnym błędnie napisali "inteligencja". Trochę niepomyślne, ale to tylko literówka, prawda? Następnie zauważysz, że ekran konfiguracji używa pól wyboru, w których powinny znajdować się przyciski radiowe, a niektóre skróty klawiaturowe nie działają. Teraz żadna z tych rzeczy nie jest wielkim problemem, ale gdy błędy się sumują, zaczynasz zastanawiać się nad programistami. Jeśli nie potrafią dobrze prostych rzeczy, jakie są szanse, że ich sztuczna inteligencja naprawdę może znaleźć i naprawić coś tak trudnego, jak problemy ze współbieżnością? Mogli być geniuszami, którzy byli tak skoncentrowani na tym, aby sztuczna inteligencja była niesamowicie świetna, że nie zauważyli tych trywialnych rzeczy, a bez "wybrednych testerów" wskazujących problemy, w końcu je znalazłeś. A teraz kwestionujesz kompetencje programistów. Tak więc, jakkolwiek dziwnie może to zabrzmieć, ci testerzy, którzy wydają się zdeterminowani, aby ujawnić każdy mały błąd w twoim kodzie, naprawdę są twoimi przyjaciółmi.

Jeden binarny

Widziałem kilka projektów, w których kompilacja przepisuje część kodu, aby wygenerować niestandardowy plik binarny dla każdego środowiska docelowego. To zawsze komplikuje sprawę bardziej niż powinno i stwarza ryzyko, że zespół może nie mieć spójnych wersji w każdej instalacji. Co najmniej obejmuje tworzenie wielu, niemal identycznych kopii oprogramowania, z których każda musi być następnie wdrożona we właściwym miejscu. Oznacza to więcej ruchomych części niż to konieczne, co oznacza więcej możliwości popełnienia błędu. Kiedyś pracowałem w zespole, w którym każda zmiana właściwości musiała zostać zaewidencjonowana dla pełnego cyklu kompilacji, więc testerzy czekali, ilekroć potrzebowali drobnej korekty (czy wspominałem, że kompilacja również trwała zbyt długo?). Pracowałem również w zespole, w którym administratorzy systemu nalegali na przebudowanie od zera do produkcji (używając tych samych skryptów co my), co oznaczało, że nie mieliśmy dowodu na to, że wersja produkcyjna była tą, która przeszła testy. I tak dalej. Zasada jest prosta: zbuduj jeden plik binarny, który możesz identyfikować i promować na wszystkich etapach potoku wydania. Trzymaj w środowisku szczegóły specyficzne dla środowiska. Może to oznaczać na przykład przechowywanie ich w kontenerze komponentów, w znanym pliku lub w ścieżce. Jeśli Twój zespół ma kompilację zmieniającą kod lub przechowuje wszystkie ustawienia docelowe w kodzie, sugeruje to, że nikt nie przemyślał projektu wystarczająco dokładnie, aby oddzielić te funkcje, które są kluczowe dla aplikacji, od tych, które są specyficzne dla platformy. Albo może być gorzej: zespół wie, co robić, ale nie może nadać priorytetu wysiłkom związanym z wprowadzeniem zmiany. Oczywiście są wyjątki: możesz budować dla celów, które mają znacząco różne ograniczenia zasobów, ale nie dotyczy to większości z nas, którzy piszą aplikacje typu "baza danych na ekran iz powrotem". Ewentualnie możesz żyć z jakimś starym bałaganem, który jest teraz zbyt trudny do naprawienia. W takich przypadkach musisz poruszać się stopniowo, ale zacznij jak najszybciej. I jeszcze jedno: zachowaj również wersję informacji o środowisku. Nie ma nic gorszego niż zerwanie konfiguracji środowiska i niemożność ustalenia, co się zmieniło. Informacje o środowisku powinny być wersjonowane oddzielnie od kodu, ponieważ będą się zmieniać w różnym tempie i z różnych powodów. Niektóre zespoły używają do tego rozproszonych systemów kontroli wersji (takich jak bazar i git), ponieważ ułatwiają one wypychanie zmian dokonanych w środowiskach produkcyjnych - co nieuchronnie się zdarza - z powrotem do repozytorium.

Tylko Kod mówi prawdę

Ostateczna semantyka programu jest określona przez działający kod. Jeśli jest to tylko w formie binarnej, będzie to trudna lektura! Kod źródłowy powinien być jednak dostępny, jeśli jest to twój program, typowy komercyjny program rozwojowy, projekt open source lub kod w dynamicznie interpretowanym języku. Kiedy spojrzysz na kod źródłowy, znaczenie programu powinno być oczywiste. Aby wiedzieć, co robi program, ostatecznie wszystko, na co możesz patrzeć, to źródło. Nawet najdokładniejszy dokument wymagań nie mówi całej prawdy: nie zawiera szczegółowej historii tego, co program faktycznie robi, a jedynie intencje wysokiego poziomu analityka wymagań. Dokument projektowy może zawierać planowany projekt, ale nie będzie w nim niezbędnych szczegółów implementacji. Te dokumenty mogły utracić synchronizację z bieżącą implementacją… lub po prostu zostały utracone. Albo nigdy nie napisane w pierwszej kolejności. Kod źródłowy może być jedyną rzeczą, jaka pozostała. Mając to na uwadze, zadaj sobie pytanie, jak wyraźnie Twój kod mówi Tobie lub innemu programiście, co robi. Możesz powiedzieć: "Och, moje komentarze powiedzą ci wszystko, co musisz wiedzieć". Pamiętaj jednak, że komentarze nie uruchamiają kodu. Mogą być tak samo błędne, jak inne formy dokumentacji. Istnieje tradycja mówiąca, że komentarze są bezwarunkowo dobrą rzeczą, więc niektórzy programiści bezdyskusyjnie piszą coraz więcej komentarzy, nawet powtarzając i wyjaśniając ciekawostki już oczywiste w kodzie. To zły sposób wyjaśniania kodu. Jeśli Twój kod wymaga komentarzy, rozważ jego refaktoryzację, aby tak nie było. Długie komentarze mogą zaśmiecać miejsce na ekranie, a nawet mogą zostać automatycznie ukryte przez Twoje IDE. Jeśli chcesz wyjaśnić zmianę, zrób to w komunikacie meldowania w systemie kontroli wersji, a nie w kodzie. Co możesz zrobić, aby Twój kod mówił prawdę tak wyraźnie, jak to tylko możliwe? Staraj się o dobre imię. Zbuduj swój kod pod kątem spójnej funkcjonalności, co również ułatwia nazewnictwo. Odłącz swój kod, aby uzyskać ortogonalność. Napisz testy automatyczne wyjaśniające zamierzone zachowanie i sprawdź interfejsy. Refaktoryzuj bezlitośnie, gdy nauczysz się kodować prostsze, lepsze rozwiązanie. Uczyń swój kod tak prostym, jak to tylko możliwe do odczytania i zrozumienia. Traktuj swój kod jak każdą inną kompozycję, taką jak wiersz, esej, publiczny blog lub ważny e-mail. Starannie twórz to, co wyrażasz, aby robiło to, co powinno i jak najbardziej bezpośrednio komunikowało to, co robi; aby nadal komunikował twoją intencję, gdy nie ma cię już w pobliżu. Pamiętaj, że przydatny kod jest używany znacznie dłużej niż kiedykolwiek zamierzano. Programiści utrzymania będą Ci wdzięczni. A jeśli jesteś programistą utrzymania ruchu i kod, nad którym pracujesz, nie mówi łatwo prawdy, zastosuj wyżej wymienione wytyczne w sposób proaktywny. Wprowadź trochę zdrowego rozsądku w kodeksie i zachowaj swój własny zdrowy rozsądek.

Posiadaj (i refaktoruj) kompilację

Nierzadko zdarza się, że zespoły, które skądinąd są wysoce zdyscyplinowane w zakresie praktyk kodowania, zaniedbują tworzenie skryptów, albo z przekonania, że są one jedynie nieistotnym szczegółem, albo z obawy, że są one złożone i należy się nimi opiekować kult inżynieria wydania. Skrypty kompilacji, których nie można konserwować z duplikacjami i błędami, powodują problemy o takim samym rozmiarze, jak te w kodzie o słabych czynnikach. Jednym z powodów, dla których zdyscyplinowani, wykwalifikowani programiści traktują kompilację jako coś drugorzędnego w swojej pracy, jest to, że skrypty kompilacji są często pisane w innym języku niż kod źródłowy. Innym jest to, że kompilacja nie jest tak naprawdę "kodem". Te uzasadnienia są sprzeczne z rzeczywistością, w której większość programistów lubi uczyć się nowych języków, a kompilacja jest tym, co tworzy wykonywalne artefakty dla programistów i użytkowników końcowych do testowania i uruchamiania. Kod jest bezużyteczny bez kompilacji, a kompilacja definiuje architekturę komponentów aplikacji. Kompilacja jest istotną częścią procesu programowania, a decyzje dotyczące procesu kompilacji mogą uprościć kod i kodowanie. Skrypty kompilacji napisane przy użyciu niewłaściwych idiomów są trudne do utrzymania, a co ważniejsze, do poprawy. Warto poświęcić trochę czasu na zrozumienie właściwego sposobu na zmianę. Błędy mogą pojawić się, gdy aplikacja jest budowana z niewłaściwą wersją zależności lub gdy konfiguracja czasu kompilacji jest nieprawidłowa. Tradycyjnie testowanie zawsze pozostawiano zespołowi ds. zapewniania jakości. Teraz zdajemy sobie sprawę, że testowanie podczas kodowania jest niezbędne, aby móc dostarczać wartość w przewidywalny sposób. W podobny sposób proces kompilacji musi być własnością zespołu programistów. Zrozumienie kompilacji może uprościć cały cykl rozwoju i obniżyć koszty. Prosta do wykonania kompilacja umożliwia nowemu programiście szybkie i łatwe rozpoczęcie pracy. Automatyzacja konfiguracji w kompilacji może umożliwić uzyskanie spójnych wyników, gdy nad projektem pracuje wiele osób, unikając rozmowy "to działa dla mnie". Wiele narzędzi do kompilacji umożliwia generowanie raportów dotyczących jakości kodu, co pozwala wcześnie wykryć potencjalne problemy. Poświęcając czas na zrozumienie, jak stworzyć własną wersję, możesz pomóc sobie i wszystkim innym członkom zespołu. Możesz skupić się na funkcjach kodowania, przynosząc korzyści interesariuszom i sprawiając, że praca staje się przyjemniejsza. Dowiedz się wystarczająco dużo o procesie kompilacji, aby wiedzieć, kiedy i jak wprowadzać zmiany. Skrypty kompilacji to kod. Są zbyt ważne, aby pozostawić je komuś innemu, choćby dlatego, że aplikacja nie jest kompletna, dopóki nie zostanie zbudowana. Zadanie programowania nie jest zakończone, dopóki nie dostarczymy działającego oprogramowania.

Połącz program i poczuj przepływ

Wyobraź sobie, że jesteś całkowicie pochłonięty tym, co robisz - skupiony, oddany i zaangażowany. Być może straciłeś poczucie czasu. Prawdopodobnie czujesz się szczęśliwy. Doświadczasz przepływu. Trudno jest zarówno osiągnąć, jak i utrzymać przepływ dla całego zespołu programistów, ponieważ jest tak wiele przerw, interakcji i innych zakłóceń, które mogą go łatwo zepsuć. Jeśli ćwiczyłeś już programowanie w parach, prawdopodobnie wiesz, jak parowanie wpływa na przepływ. Jeśli nie, chcemy wykorzystać nasze doświadczenia, aby zmotywować Cię do rozpoczęcia już teraz! Aby odnieść sukces w programowaniu w parach, zarówno poszczególni członkowie zespołu, jak i zespół jako całość muszą włożyć pewien wysiłek. Jako członek zespołu bądź cierpliwy wobec programistów mniej doświadczonych niż Ty. Zmierz się ze swoimi obawami, że będziesz zastraszany przez bardziej wykwalifikowanych programistów. Uświadom sobie, że ludzie są inni i doceniaj to. Bądź świadomy swoich mocnych i słabych stron, a także innych członków zespołu. Możesz być zaskoczony, jak wiele możesz się nauczyć od swoich kolegów. Jako zespół wprowadź programowanie w parach, aby promować dystrybucję umiejętności i wiedzy w całym projekcie. Powinieneś rozwiązywać swoje zadania w parach i często rotować parami i zadaniami. Ustalcie zasadę rotacji. Odłóż regułę na bok lub dostosuj ją w razie potrzeby. Z naszego doświadczenia wynika, że niekoniecznie musisz ukończyć zadanie przed zamianą go na inną parę. Przerwanie zadania w celu przekazania go innej parze może wydawać się sprzeczne z intuicją, ale odkryliśmy, że to działa. Istnieje wiele sytuacji, w których przepływ może zostać przerwany, ale programowanie w parach pomaga go utrzymać:

•  Zmniejsz "współczynnik ciężarówek". To trochę makabryczny eksperyment myślowy, ale ilu członków twojego zespołu musiałoby potrącić ciężarówka, zanim zespół nie byłby w stanie ukończyć ostatecznego elementu dostawy? Innymi słowy, w jakim stopniu Twoja dostawa zależy od niektórych członków zespołu? Czy wiedza jest uprzywilejowana czy dzielona? Jeśli rotujesz zadaniami w parach, zawsze jest ktoś inny, kto ma wiedzę i może wykonać pracę. Na przepływ Twojego zespołu nie ma tak dużego wpływu "czynnik ciężarówek".
•  Skutecznie rozwiązuj problemy. Jeśli programujesz w parach i napotkasz trudny problem, zawsze masz z kim o tym porozmawiać. Taki dialog z większym prawdopodobieństwem otworzy możliwości, niż gdy sam utkniesz. W miarę rotacji pracy Twoje rozwiązanie zostanie ponownie przeanalizowane i rozważone przez następną parę, więc nie ma znaczenia, czy początkowo nie wybrałeś optymalnego rozwiązania.
•  Płynna integracja. Jeśli twoje obecne zadanie obejmuje wywołanie innego fragmentu kodu, masz nadzieję, że nazwy metod, dokumentacji i testów są wystarczająco opisowe, aby dać ci pojęcie o tym, co robi. Jeśli nie, sparowanie z programistą, który był zaangażowany w pisanie tego kodu, zapewni lepszy przegląd i szybszą integrację z własnym kodem. Ponadto możesz wykorzystać dyskusję jako okazję do ulepszenia nazewnictwa, dokumentów i testowania.
•  Łagodzenie przerw. Jeśli ktoś przyjdzie, aby zadać Ci pytanie, zadzwoni Twój telefon, będziesz musiał odpowiedzieć na pilną wiadomość e-mail lub będziesz musiał wziąć udział w spotkaniu, Twój partner w programowaniu w parach może dalej kodować. Kiedy wrócisz, twój partner nadal jest w ruchu, a ty szybko go dogonisz i dołączysz do niego.
•  Szybko przywołaj nowych członków zespołu. Dzięki programowaniu w parach oraz odpowiedniej rotacji par i zadań nowicjusze szybko poznają zarówno kod, jak i pozostałych członków zespołu.

Flow sprawia, że jesteś niesamowicie produktywny. Ale jest też wrażliwy. Zrób, co możesz, aby go zdobyć i trzymaj się go, gdy go zdobędziesz!


Preferuj typy specyficzne dla domeny od typów pierwotnych

23 września 1999 roku Mars Climate Orbiter o wartości 327,6 miliona dolarów został utracony podczas wchodzenia na orbitę wokół Marsa z powodu błędu oprogramowania na Ziemi. Błąd został później nazwany pomieszaniem metryk. Oprogramowanie stacji naziemnej działało w funtach, podczas gdy statek kosmiczny oczekiwał niutonów, co doprowadziło stację naziemną do niedoszacowania mocy silników odrzutowych statku kosmicznego o współczynnik 4,45. Jest to jeden z wielu przykładów awarii oprogramowania, którym można było zapobiec, gdyby zastosowano silniejsze i bardziej specyficzne dla domeny typowanie. Jest to również przykład uzasadnienia wielu funkcji języka Ada, którego jednym z głównych celów projektowych było wdrożenie wbudowanego oprogramowania krytycznego dla bezpieczeństwa. Ada ma silne typowanie ze statycznym sprawdzaniem zarówno typów pierwotnych, jak i typów zdefiniowanych przez użytkownika:

type Velocity_In_Knots is new Float range 0.0 .. 500.00;
type Distance_In_Nautical_Miles is new Float range 0.0 .. 3000.00;
Velocity: Velocity_In_Knots;
Distance: Distance_In_Nautical_Miles;
Some_Number: Float;
Some_Number:= Distance + Velocity; -- Will be caught by the compiler as a type error.

Deweloperzy w mniej wymagających domenach mogą również skorzystać na zastosowaniu typowania bardziej specyficznego dla domeny, gdzie w przeciwnym razie mogliby nadal używać prymitywnych typów danych oferowanych przez język i jego biblioteki, takich jak ciągi i pływaki. W Javie, C++, Pythonie i innych nowoczesnych językach abstrakcyjny typ danych jest znany jako class. Używanie klas takich jak Velocity_In_Knots i Distance_In_Nautical_Miles dodaje wiele wartości w odniesieniu do jakości kodu:

•  Kod staje się bardziej czytelny, ponieważ wyraża koncepcje domeny, a nie tylko Float czy String.
•  Kod staje się bardziej testowalny, ponieważ kod zawiera zachowanie, które można łatwo przetestować.
•  Kod ułatwia ponowne użycie w różnych aplikacjach i systemach.

Podejście to jest równie ważne dla użytkowników zarówno statycznie, jak i dynamicznie typowanych języków. Jedyna różnica polega na tym, że programiści używający języków statycznie typowanych uzyskują pewną pomoc od kompilatora, podczas gdy ci, którzy stosują języki typowane dynamicznie, częściej polegają na swoich testach jednostkowych. Styl sprawdzania może być inny, ale motywacja i styl wyrażania nie. Morał polega na tym, aby zacząć eksplorować typy specyficzne dla domeny w celu opracowania wysokiej jakości oprogramowania.

Zapobiegaj błędom

Komunikaty o błędach to najbardziej krytyczne interakcje między użytkownikiem a resztą systemu. Zdarzają się, gdy komunikacja między użytkownikiem a systemem zbliża się do punktu krytycznego. Łatwo jest myśleć o błędzie jako spowodowanym niewłaściwym wejściem użytkownika. Ale ludzie popełniają błędy w przewidywalny, systematyczny sposób. Dzięki temu można "debugować" komunikację między użytkownikiem a resztą systemu, tak jak robi się to między innymi komponentami systemu. Załóżmy na przykład, że chcesz, aby użytkownik wprowadził datę z dozwolonego zakresu. Zamiast pozwalać użytkownikowi na wprowadzenie dowolnej daty, lepiej jest zaoferować urządzenie, takie jak lista lub kalendarz pokazujący tylko dozwolone daty. Eliminuje to jakąkolwiek szansę na wprowadzenie przez użytkownika daty spoza zakresu. Błędy formatowania to kolejny powszechny problem. Na przykład, jeśli użytkownikowi zostanie wyświetlone pole tekstowe Data i wprowadzi jednoznaczną datę, taką jak "29 lipca 2012 r.", nierozsądne jest odrzucenie go tylko dlatego, że nie jest w preferowanym formacie (np. "DD/MM/ RRRR"). Jeszcze gorzej jest odrzucić "29.07.2012", ponieważ zawiera dodatkowe spacje - ten rodzaj problemu jest szczególnie trudny do zrozumienia dla użytkowników, ponieważ data wydaje się być w pożądanym formacie. Ten błąd występuje, ponieważ łatwiej jest odrzucić datę niż przeanalizować trzy lub cztery najpopularniejsze formaty daty. Tego rodzaju drobne błędy prowadzą do frustracji użytkownika, co z kolei prowadzi do dodatkowych błędów, gdy użytkownik traci koncentrację. Zamiast tego szanuj preferencje użytkowników dotyczące wprowadzania informacji, a nie danych. Innym sposobem na uniknięcie błędów formatowania jest oferowanie wskazówek - na przykład z etykietą w polu z żądanym formatem ("DD/MM/RRRR"). Inną wskazówką może być podzielenie pola na trzy dwu-, dwu- i czteroznakowe pola tekstowe. Wskazówki różnią się od instrukcji: wskazówki są zwykle podpowiedziami; instrukcje są pełne. Wskazówki pojawiają się w punkcie interakcji; instrukcje pojawiają się przed punktem interakcji. Wskazówki zapewniają kontekst; instrukcje dyktują użycie. Ogólnie instrukcje nie są skuteczne w zapobieganiu błędom. Użytkownicy mają tendencję do zakładania, że interfejsy będą działać zgodnie z ich dotychczasowymi doświadczeniami ("Z pewnością wszyscy wiedzą, co oznacza "29 lipca 2012 r.""?). Więc instrukcje są nieprzeczytane. Sygnały odsuwają użytkowników od błędów. Innym sposobem na uniknięcie błędów jest oferowanie wartości domyślnych. Na przykład użytkownicy zazwyczaj wprowadzają wartości odpowiadające dzisiaj, jutro, moim urodzinom, moim ostatecznym terminom lub dacie, którą wprowadziłem podczas ostatniego korzystania z tego formularza. W zależności od kontekstu, jeden z nich może być dobrym wyborem jako inteligentne ustawienie domyślne. Niezależnie od przyczyny systemy powinny być odporne na błędy. Możesz to ułatwić, zapewniając wiele poziomów cofania wszystkich działań - a w szczególności działań, które mogą potencjalnie zniszczyć lub zmienić dane użytkowników. Rejestrowanie i analizowanie działań cofania może również wskazywać, gdzie interfejs wciąga użytkowników w nieświadome błędy, takie jak uporczywe klikanie "niewłaściwego" przycisku. Błędy te są często powodowane przez wprowadzające w błąd wskazówki lub sekwencje interakcji, które można przeprojektować, aby zapobiec dalszym błędom. Niezależnie od przyjętego podejścia, większość błędów ma charakter systematyczny - wynik nieporozumień między użytkownikiem a oprogramowaniem. Zrozumienie, jak użytkownicy myślą, interpretują informacje, podejmują decyzje i wprowadzają dane, pomoże w debugowaniu interakcji między oprogramowaniem a użytkownikami.

Profesjonalny programista

Kim jest zawodowy programista? Najważniejszą cechą profesjonalnego programisty jest osobista odpowiedzialność. Profesjonalni programiści biorą odpowiedzialność za swoją karierę, szacunki, zobowiązania harmonogramowe, błędy i jakość wykonania. Profesjonalny programista nie zrzuca tej odpowiedzialności na innych.

•  Jeśli jesteś profesjonalistą, odpowiadasz za własną karierę. Jesteś odpowiedzialny za czytanie i uczenie się. Jesteś odpowiedzialny za bycie na bieżąco z branżą i technologią. Zbyt wielu programistów uważa, że ich przeszkolenie jest zadaniem ich pracodawcy. Przepraszam, to jest po prostu martwe. Czy uważasz, że lekarze zachowują się w ten sposób? Czy uważasz, że prawnicy zachowują się w ten sposób? Nie, ćwiczą się w swoim własnym czasie i własnej pięciocentówce. Większość czasu poza godzinami pracy spędzają na czytaniu dzienników i decyzji. Utrzymują się na bieżąco. I my też musimy. Relacja między Tobą a Twoim pracodawcą jest ładnie określona w Twojej umowie o pracę. Krótko mówiąc: twój pracodawca obiecuje ci zapłacić, a ty obiecujesz wykonać dobrą robotę.

•  Profesjonaliści biorą odpowiedzialność za napisany przez siebie kod. Nie publikują kodu, jeśli nie wiedzą, że działa. Pomyśl o tym przez chwilę. Jak możesz uważać się za profesjonalistę, jeśli chcesz wydać kod, którego nie jesteś pewien? Profesjonalni programiści oczekują, że kontrola jakości niczego nie znajdzie, ponieważ nie publikują swojego kodu, dopóki go dokładnie nie przetestują. Oczywiście QA napotka pewne problemy, bo nikt nie jest doskonały. Ale jako profesjonaliści, musimy nastawić się na to, że nie pozostawimy niczego do odnalezienia przez kontrolę jakości.

•  Profesjonaliści to gracze zespołowy. Biorą odpowiedzialność za wyniki całego zespołu, a nie tylko za własną pracę. Pomagają sobie nawzajem, uczą się nawzajem, uczą się od siebie, a nawet kryją się nawzajem w razie potrzeby. Kiedy jeden z członków drużyny upada, pozostali wkraczają, wiedząc, że pewnego dnia to oni będą potrzebować osłony.

•  Profesjonaliści nie tolerują dużych list błędów. Ogromna lista błędów jest niechlujna. Systemy z tysiącami spraw w bazie danych śledzenia problemów to tragedia nieostrożności. Rzeczywiście, w większości projektów sama potrzeba systemu śledzenia problemów jest objawem nieostrożności. Tylko największe systemy powinny mieć tak długą listę błędów, że do zarządzania nimi wymagana jest automatyzacja.

•  Profesjonaliści nie robią bałaganu. Są dumni ze swojego wykonania. Utrzymują swój kod czysty, dobrze zorganizowany i łatwy do odczytania. Przestrzegają uzgodnionych standardów i najlepszych praktyk. Nigdy się nie spieszą. Wyobraź sobie, że doświadczasz przebywania poza ciałem, obserwując, jak lekarz przeprowadza na tobie operację na otwartym sercu. Ten lekarz ma termin (w dosłownym znaczeniu). Musi skończyć, zanim maszyna do omijania płuc i serca uszkodzi zbyt wiele komórek krwi. Jak chcesz, żeby się zachowywał? Czy chcesz, żeby zachowywał się jak typowy programista, pędząc i robiąc bałagan? Czy chcesz, żeby powiedział: "Wrócę i naprawię to później"? A może chcesz, żeby ostrożnie trzymał się swojej dyscypliny, nie spiesząc się, mając pewność, że jego podejście jest najlepszym podejściem, jakie może przyjąć. Chcesz bałaganu, czy profesjonalizmu?

Odpowiedzialni są profesjonaliści. Biorą odpowiedzialność za własną karierę. Biorą odpowiedzialność za upewnienie się, że ich kod działa poprawnie. Biorą odpowiedzialność za jakość ich wykonania. Nie porzucają swoich zasad, gdy zbliżają się terminy. Rzeczywiście, kiedy presja rośnie, profesjonaliści coraz mocniej trzymają się dyscyplin, o których wiedzą, że są właściwe.

Umieść wszystko pod kontrolą wersji

Umieść wszystko we wszystkich swoich projektach pod kontrolą wersji. Potrzebne zasoby są tam: bezpłatne narzędzia, takie jak Subversion, Git, Mercurial i CVS; dużo miejsca na dysku; tanie i wydajne serwery; wszechobecne sieci; a nawet usługi hostingu projektów. Po zainstalowaniu oprogramowania do kontroli wersji, aby umieścić swoją pracę w repozytorium, wystarczy wydać odpowiednią komendę w czystym katalogu zawierającym Twój kod. A do nauczenia są tylko dwie nowe podstawowe operacje: zatwierdzasz zmiany kodu w repozytorium i aktualizujesz działającą wersję projektu o wersję z repozytorium. Gdy Twój projekt znajdzie się pod kontrolą wersji, możesz oczywiście śledzić jego historię, zobaczyć, kto napisał jaki kod i odwoływać się do pliku lub wersji projektu za pomocą unikalnego identyfikatora. Co ważniejsze, możesz bez obaw dokonywać pogrubionych zmian w kodzie - koniec z zakomentowanym kodem na wypadek, gdybyś go potrzebował w przyszłości, ponieważ stara wersja jest bezpiecznie przechowywana w repozytorium. Możesz (i powinieneś) oznaczyć wersję oprogramowania nazwą symboliczną, aby w przyszłości móc łatwo ponownie sprawdzić dokładną wersję oprogramowania, z którego korzysta Twój klient. Możesz tworzyć gałęzie rozwoju równoległego: większość projektów ma aktywną gałąź rozwoju i jedną lub więcej gałęzi utrzymania dla wydanych wersji, które są aktywnie wspierane. System kontroli wersji minimalizuje tarcia między programistami. Kiedy programiści pracują nad niezależnymi częściami oprogramowania, integrują się one niemal za pomocą magii. Kiedy nadepną sobie nawzajem na palce, system zauważa i pozwala rozwiązać konflikty. Dzięki dodatkowej konfiguracji system może powiadomić wszystkich programistów o każdej wprowadzonej zmianie, ustanawiając wspólne zrozumienie postępu projektu. Podczas konfigurowania projektu nie bądź skąpy: umieść wszystkie zasoby projektu pod kontrolą wersji. Oprócz kodu źródłowego dołącz dokumentację, narzędzia, skrypty do budowania, przypadki testowe, grafikę, a nawet biblioteki. Gdy cały projekt jest bezpiecznie schowany w (regularnie tworzonym) repozytorium, potencjalne szkody związane z utratą dysku lub danych są zminimalizowane. Konfiguracja do programowania na nowej maszynie polega po prostu na pobraniu projektu z repozytorium. Upraszcza to dystrybucję, budowanie i testowanie kodu na różnych platformach na każdym komputerze pojedyncze polecenie aktualizacji zapewni, że oprogramowanie będzie w aktualnej wersji. Gdy już zobaczysz piękno pracy z systemem kontroli wersji, przestrzeganie kilku zasad sprawi, że Ty i Twój zespół będziecie jeszcze bardziej efektywni:

•  Zatwierdź każdą logiczną zmianę w oddzielnej operacji. Łączenie wielu zmian razem w jednym zatwierdzeniu utrudni rozplątanie ich w funkcji. Jest to szczególnie ważne w przypadku refaktoryzacji całego projektu lub zmian stylu, które mogą łatwo przesłonić inne modyfikacje.

•  Dołącz do każdego zatwierdzenia z informacją wyjaśniającą. Opisz co najmniej zwięźle, co zmieniłeś, ale jeśli chcesz również zapisać uzasadnienie zmiany, to jest to najlepsze miejsce do przechowywania tego.

•  Wreszcie, unikaj zatwierdzania kodu, który zepsuje kompilację projektu, w przeciwnym razie staniesz się niepopularny wśród innych programistów projektu.

Życie w systemie kontroli wersji jest zbyt dobre, aby je zrujnować łatwymi do uniknięcia błędami.

Odłóż mysz i odsuń się od klawiatury

Przez wiele godzin skupiałeś się na jakimś trudnym problemie i nie widać żadnego rozwiązania. Wstajesz więc, żeby rozprostować nogi lub uderzyć w automaty, a w drodze powrotnej odpowiedź nagle staje się oczywista. Czy ten scenariusz brzmi znajomo? Czy zastanawiałeś się kiedyś, dlaczego tak się dzieje? Sztuczka polega na tym, że podczas kodowania aktywna jest logiczna część mózgu, a twórcza strona jest odcięta. Nie może ci nic zaprezentować, dopóki strona logiczna nie zrobi sobie przerwy. Oto przykład z życia: czyściłem stary kod i natknąłem się na "interesującą" metodę. Został zaprojektowany w celu sprawdzenia, czy ciąg zawiera prawidłową godzinę przy użyciu formatu gg:mm:ss xx, gdzie gg reprezentuje godzinę, mm reprezentuje minuty, ss reprezentuje sekundy, a xx to AM lub PM. Metoda użyła następującego kodu do konwersji dwóch znaków (reprezentujących godzinę) na liczbę i sprawdzenia, czy mieści się w odpowiednim zakresie:

try {
Integer.parseInt(time.substring(0, 2));
} catch (Exception x) {
return false;
}
if (Integer.parseInt(time.substring(0, 2)) > 12) {
return false;
}

Ten sam kod pojawił się jeszcze dwukrotnie, z odpowiednimi zmianami przesunięcia znaków i górnego limitu, aby przetestować minuty i sekundy. Metoda zakończyła się tymi wierszami, aby sprawdzić AM i PM:

if (!time.substring(9, 11).equals("AM") &
!time.substring(9, 11).equals("PM")) {
return false;
}

Jeśli żadne z tych porównań nie zakończyło się niepowodzeniem, zwracając fałsz, metoda zwracała prawdę. Jeśli poprzedni kod wydaje się rozwlekły i trudny do naśladowania, nie martw się. Też tak myślałem - co oznaczało, że znalazłem coś, co warto posprzątać. Zrefaktorowałem go i napisałem kilka testów jednostkowych, aby upewnić się, że nadal działa. Kiedy skończyłem, byłem zadowolony z wyników. Nowa wersja była łatwa do odczytania, o połowę mniejsza i dokładniejsza, ponieważ oryginalny kod testował tylko górna granica godzin, minut i sekund. Przygotowując się do pracy następnego dnia, w mojej głowie pojawił się pomysł: dlaczego nie sprawdzić poprawności ciągu za pomocą wyrażenia regularnego? Po kilku minutach pisania miałem działającą implementację w zaledwie jednej linii kodu. Oto on:

public static boolean validateTime(String time) {
return time.matches("(0[1-9]|1[0-2]):[0-5][0-9]:[0-5][0-9] ([AP]M)");
}

Nie chodzi o to, że ostatecznie zamieniłem ponad 30 linijek kodu na tylko jeden. Chodzi o to, że dopóki nie oderwałem się od komputera, myślałem, że moja pierwsza próba jest najlepszym rozwiązaniem problemu. Więc następnym razem, gdy napotkasz paskudny problem, zrób sobie przysługę. Kiedy już naprawdę zrozumiesz problem, zrób coś, co wiąże się z kreatywną stroną swojego mózgu, naszkicuj problem, posłuchaj muzyki lub po prostu wybierz się na spacer na zewnątrz. Czasami najlepszą rzeczą, jaką możesz zrobić, aby rozwiązać problem, jest odłożenie myszy i odsunięcie się od klawiatury.

Przeczytaj kod

My programiści jesteśmy dziwnymi stworzeniami. Uwielbiamy pisać kod. Ale jeśli chodzi o czytanie, zwykle się wstydzimy. W końcu pisanie kodu jest o wiele przyjemniejsze, a czytanie kodu jest trudne - czasami prawie niemożliwe. Czytanie kodu innych ludzi jest szczególnie trudne. Niekoniecznie dlatego, że kod innych ludzi jest zły, ale dlatego, że prawdopodobnie myślą i rozwiązują problemy w inny sposób niż ty. Ale czy kiedykolwiek myślałeś, że czytanie cudzego kodu może poprawić twój własny? Następnym razem, gdy przeczytasz jakiś kod, zatrzymaj się i zastanów przez chwilę. Czy kod jest łatwy czy trudny do odczytania? Jeśli jest trudny do odczytania, dlaczego tak jest? Czy formatowanie jest słabe? Czy nazewnictwo jest niespójne lub nielogiczne? Czy w tym samym fragmencie kodu znajduje się kilka problemów? Być może wybór języka uniemożliwia odczytanie kodu. Staraj się uczyć na błędach innych ludzi, aby Twój kod nie zawierał tych samych. Możesz otrzymać kilka niespodzianek. Na przykład techniki łamania zależności mogą być dobre w przypadku niskiego sprzężenia, ale czasami mogą również utrudniać odczytywanie kodu. I to, co niektórzy nazywają eleganckim kodem, inni nazywają nieczytelnym. Jeśli kod jest łatwy do odczytania, zatrzymaj się, aby zobaczyć, czy jest coś przydatnego, czego możesz się z niego nauczyć. Być może istnieje wzorzec projektowy, o którym nie wiesz lub miałeś trudności z wdrożeniem. Być może metody są krótsze, a ich nazwy bardziej wyraziste niż twoje. Niektóre projekty open source są pełne dobrych przykładów na to, jak pisać genialny, czytelny kod - podczas gdy inne służą jako przykłady dokładnie odwrotnego! Sprawdź niektóre z ich kodu i spójrz. Czytanie własnego starego kodu z projektu, nad którym obecnie nie pracujesz, może być pouczającym doświadczeniem. Zacznij od najstarszego kodu i przejdź do teraźniejszości. Prawdopodobnie przekonasz się, że wcale nie jest tak łatwe do odczytania, jak wtedy, gdy go pisałeś. Twój wczesny kod może również mieć pewną żenującą wartość rozrywkową, podobnie jak przypomnienie o wszystkich rzeczach, które powiedziałeś, gdy wczoraj piłeś w pubie. Zobacz, jak rozwijałeś swoje umiejętności przez lata - to może być naprawdę motywujące. Obserwuj, które obszary kodu są trudne do odczytania i zastanów się, czy nadal piszesz kod w ten sam sposób. Więc następnym razem, gdy poczujesz potrzebę poprawy swoich umiejętności programistycznych, nie czytaj kolejnej książki. Przeczytaj kod.

Przeczytaj nauki humanistyczne

We wszystkich, z wyjątkiem najmniejszych projektów rozwojowych, ludzie pracują z ludźmi. We wszystkich poza najbardziej abstrakcyjną dziedziną badań ludzie piszą oprogramowanie dla ludzi, aby wspierać ich w realizacji ich celów. Ludzie piszą oprogramowanie z ludźmi dla ludzi. To biznes dla ludzi. Niestety, to, czego zbyt często uczy się programistów, bardzo słabo przygotowuje ich do radzenia sobie z ludźmi, dla których pracują. Na szczęście istnieje cała dziedzina nauki, która może pomóc. Na przykład Ludwig Wittgenstein przedstawia bardzo dobry argument w Philosophical Investigations (Wiley-Blackwell) i gdzie indziej, że żaden język, którego używamy do rozmawiania ze sobą, nie jest - nie może być - formatem serializacji służącym do wydobycia myśli, idei lub obrazu głowy jednej osoby w głowę innej. Już teraz powinniśmy wystrzegać się nieporozumień, kiedy "zbieramy wymagania". Wittgenstein pokazuje również, że nasza zdolność rozumienia się nawzajem wcale nie wynika ze wspólnych definicji, lecz ze wspólnego doświadczenia, z jakiejś formy życia. Może to być jeden z powodów, dla których programiści, którzy są zakorzenieni w swojej problematycznej domenie, radzą sobie lepiej niż ci, którzy się od niej wyróżniają. Lakoff i Johnson przedstawiają nam katalog Metaphors We Live By (University of Chicago Press), sugerując, że język jest w dużej mierze metaforyczny i że te metafory oferują wgląd w to, jak rozumiemy świat. Nawet pozornie konkretne terminy, takie jak przepływy pieniężne, z którymi możemy się spotkać, mówiąc o systemie finansowym, można uznać za metaforyczne: "pieniądze to płyn". Jak ta metafora wpływa na sposób, w jaki myślimy o systemach obsługujących pieniądze? Albo możemy mówić o warstwach w stosie protokołów, z pewnym wysokim i niskim poziomem. Jest to bardzo metaforyczne: użytkownik jest "na górze", a technologia "nie działa". Ujawnia to nasze myślenie o strukturze budowanych przez nas systemów. Może to również oznaczać leniwy nawyk myślenia, z którego od czasu do czasu możemy skorzystać. Martin Heidegger dokładnie przestudiował sposoby, w jakie ludzie doświadczają narzędzi. Programiści budują i używają narzędzi, my myślimy i tworzymy oraz modyfikujemy i odtwarzamy narzędzia. Przedmiotem naszego zainteresowania są narzędzia. Ale dla jego użytkowników, jak pokazuje Heiddeger w Byciu i czasie (Harper Perennial), narzędzie staje się rzeczą niewidzialną, rozumianą tylko w użyciu. Dla użytkowników narzędzia stają się obiektem zainteresowania tylko wtedy, gdy nie działają. Tę różnicę w nacisku warto mieć na uwadze, gdy dyskutowana jest użyteczność. Eleanor Rosch obaliła arystotelesowski model kategorii, według których organizujemy nasze rozumienie świata. Kiedy programiści pytają użytkowników o ich pragnienia dotyczące systemu, zwykle pytamy o definicje zbudowane z predykatów. To dla nas bardzo wygodne. Terminy w predykatach mogą bardzo łatwo stać się atrybutami klasy lub kolumn w tabeli. Tego rodzaju kategorie są ostre, chaotyczne i uporządkowane. Niestety, jak pokazał Rosch w "Kategoriach naturalnych"*, a później działa, ludzie na ogół nie tak rozumieją świat. Rozumieją to w sposób oparty na przykładach. Niektóre przykłady, tzw. prototypy, są lepsze od innych, przez co powstałe kategorie są rozmyte, nakładają się na siebie, mogą mieć bogatą strukturę wewnętrzną. O ile nalegamy na odpowiedzi Arystotelesa, nie możemy zadawać użytkownikom właściwych pytań dotyczących świata użytkownika i będziemy mieć trudności z osiągnięciem wspólnego zrozumienia, którego potrzebujemy.

Często odkrywaj koło na nowo

Czy kiedykolwiek słyszałeś o tym lub jego odmianie? Jasne, że tak! Każdy programista i student prawdopodobnie często słyszy takie komentarze. Dlaczego jednak? Dlaczego wymyślanie koła na nowo jest tak mile widziane? Ponieważ najczęściej istniejący kod działa. Przeszedł już pewną kontrolę jakości i rygorystyczne testy i jest z powodzeniem stosowany. Co więcej, czas i wysiłek zainwestowany w odkrywanie nowych rozwiązań raczej nie opłaci się, podobnie jak wykorzystanie istniejącego produktu lub bazy kodu. Czy powinieneś zawracać sobie głowę wymyślaniem koła? Czemu? Gdy? Być może widziałeś publikacje o wzorcach w tworzeniu oprogramowania lub książki o projektowaniu oprogramowania. Te książki mogą być podkładami, niezależnie od tego, jak wspaniałe są w nich zawarte informacje. W ten sam sposób, w jaki oglądanie filmu o żeglowaniu bardzo różni się od żeglowania, tak samo jest używanie istniejącego kodu w przeciwieństwie do projektowania własnego oprogramowania od podstaw, testowania go, łamania, naprawiania i ulepszania po drodze. Ponowne wymyślanie koła to nie tylko ćwiczenie polegające na umieszczaniu konstrukcji kodu: chodzi o to, jak uzyskać dogłębną wiedzę o wewnętrznym działaniu różnych komponentów, które już istnieją. Czy wiesz, jak działają menedżerowie pamięci? Stronicowanie wirtualne? Czy mógłbyś je wdrożyć samodzielnie? Co powiesz na listy z podwójnymi linkami? Klasy tablic dynamicznych? Klienci ODBC? Czy możesz napisać graficzny interfejs użytkownika, który działa jak popularny, który znasz i lubisz? Czy możesz tworzyć własne widżety przeglądarki internetowej? Czy wiesz, kiedy napisać system multipleksowy, a kiedy wielowątkowy? Jak wybrać między bazą danych opartą na plikach czy na pamięci? Większość programistów po prostu nigdy nie stworzyła samodzielnie tego typu podstawowych implementacji oprogramowania i dlatego nie ma gruntownej wiedzy na temat ich działania. Konsekwencją jest to, że wszystkie te rodzaje oprogramowania są postrzegane jako tajemnicze czarne skrzynki, które po prostu działają. Zrozumienie samej powierzchni wody nie wystarczy, aby odsłonić ukryte pod nią niebezpieczeństwa. Nieznajomość głębszych rzeczy w tworzeniu oprogramowania ograniczy twoją zdolność tworzenia gwiezdnej pracy. Wymyślanie koła na nowo i popełnianie błędów jest bardziej wartościowe niż przybijanie go za pierwszym razem. Są lekcje wyciągnięte z prób i błędów, które mają dla nich składnik emocjonalny, których samo czytanie książki technicznej po prostu nie może dostarczyć! Wyuczone fakty i inteligencja książkowa są kluczowe, ale bycie świetnym programistą polega w równym stopniu na zdobywaniu doświadczenia, co na zbieraniu faktów. Wymyślanie koła na nowo jest tak samo ważne dla edukacji i umiejętności programisty, jak podnoszenie ciężarów dla kulturysty.

Oprzyj się pokusie wzorca singletona

Wzór Singleton rozwiązuje wiele twoich problemów. Wiesz, że potrzebujesz tylko jednej instancji. Masz gwarancję, że ta instancja zostanie zainicjowana przed użyciem. Zapewnia prostotę projektu dzięki globalnemu punktowi dostępu. Wszystko jest dobrze. Czego nie lubić w tym klasycznym wzorze projektowym? Okazuje się, że całkiem sporo. Mogą być kuszące, ale doświadczenie pokazuje, że większość singli naprawdę wyrządza więcej szkody niż pożytku. Utrudniają testowalność i szkodzą konserwacji. Niestety ta dodatkowa mądrość nie jest tak powszechna, jak powinna, a singletony nadal są nie do odparcia dla wielu programistów. Ale warto im się oprzeć: •  Często wyobraża się sobie wymaganie jednej instancji. W wielu przypadkach to czysta spekulacja, że w przyszłości nie będą potrzebne żadne dodatkowe instancje. Nadawanie takich spekulacyjnych właściwości w projekcie aplikacji z pewnością w pewnym momencie sprawi ból. Wymagania się zmienią. Dobry projekt obejmuje to. Singletony nie. •  Singletony powodują niejawne zależności między koncepcyjnie niezależnymi jednostkami kodu. Jest to problematyczne zarówno dlatego, że są ukryte, jak i dlatego, że wprowadzają niepotrzebne sprzężenie między jednostkami. Ten zapach kodu staje się ostry, gdy próbujesz napisać testy jednostkowe, które zależą od luźnego sprzężenia i możliwości selektywnego zastępowania rzeczywistej implementacji próbnej. Singletony uniemożliwiają takie proste kpiny. •  Singletony przenoszą również niejawny stan trwały, co ponownie utrudnia testowanie jednostkowe. Testowanie jednostkowe polega na tym, że testy są od siebie niezależne, dzięki czemu testy można uruchamiać w dowolnej kolejności, a program można ustawić w znany stan przed wykonaniem każdego testu jednostkowego. Gdy wprowadzisz singletony ze stanem mutowalnym, może to być trudne do osiągnięcia. Ponadto taki globalnie dostępny trwały stan utrudnia wnioskowanie o kodzie, zwłaszcza w środowisku wielowątkowym. •  Wielowątkowość wprowadza kolejne pułapki do wzorca singleton. Ponieważ proste blokowanie dostępu nie jest zbyt wydajne, popularność zyskał tak zwany podwójnie sprawdzany wzorzec blokowania (DCLP). Niestety może to być kolejna forma śmiertelnego przyciągania. Okazuje się, że w wielu językach DCLP nie jest bezpieczny dla wątków, a nawet tam, gdzie jest, nadal istnieją możliwości, aby go delikatnie pomylić. Oczyszczenie singletonów może stanowić ostateczne wyzwanie: •  Nie ma wsparcia dla jawnego zabijania singletonów. W niektórych kontekstach może to stanowić poważny problem - na przykład w architekturze wtyczek, w której wtyczkę można bezpiecznie usunąć dopiero po wyczyszczeniu wszystkich jej obiektów. •  Nie ma rozkazu niejawnego czyszczenia singletonów przy wyjściu z programu. Może to być kłopotliwe w przypadku aplikacji zawierających singletony z współzależnościami. Podczas zamykania takich aplikacji jeden singleton może uzyskać dostęp do innego, który został już zniszczony. Niektóre z tych niedociągnięć można przezwyciężyć, wprowadzając dodatkowe mechanizmy. Jednak odbywa się to kosztem dodatkowej złożoności kodu, której można było uniknąć, wybierając alternatywny projekt. Dlatego ogranicz użycie wzorca Singleton do klas, które naprawdę nie mogą być tworzone więcej niż raz. Nie używaj globalnego punktu dostępu singletona z dowolnego kodu. Zamiast tego bezpośredni dostęp do singletona powinien pochodzić tylko z kilku dobrze zdefiniowanych miejsc, skąd można go przekazywać za pośrednictwem interfejsu do innego kodu. Ten inny kod jest nieświadomy, a więc nie zależy od tego, czy interfejs jest implementowany przez klasę singletona czy inną klasę. To przerywa zależności, które uniemożliwiły testowanie jednostkowe i poprawia łatwość konserwacji. Więc następnym razem, gdy będziesz myślał o zaimplementowaniu lub uzyskaniu dostępu do singletona, mam nadzieję, że zatrzymasz się i pomyślisz jeszcze raz.

Droga do występu jest usiana brudnymi bombami kodowymi

Najczęściej dostrajanie wydajności systemu wymaga zmiany kodu. Kiedy musimy zmienić kod, każdy fragment, który jest nadmiernie złożony lub mocno powiązany, jest brudną bombą kodową, która czeka, by wykoleić wysiłek. Pierwszą ofiarą brudnego kodu będzie twój harmonogram. Jeśli droga do przodu jest gładka, łatwo będzie przewidzieć, kiedy skończysz. Nieoczekiwane spotkania z brudnym kodem bardzo utrudnią dokonanie rozsądnej prognozy. Rozważ przypadek, w którym znajdziesz gorący punkt egzekucji. Normalnym sposobem działania jest zmniejszenie siły podstawowego algorytmu. Załóżmy, że odpowiadasz na prośbę swojego menedżera o oszacowanie odpowiedzi w ciągu 3-4 godzin. Wprowadzając poprawkę, szybko zdajesz sobie sprawę, że zepsułeś zależną część. Ponieważ blisko spokrewnione rzeczy są często z konieczności sprzężone, to pęknięcie jest najprawdopodobniej oczekiwane i rozliczane. Ale co się stanie, jeśli naprawienie tej zależności spowoduje uszkodzenie innych zależnych części? Co więcej, im dalej zależność znajduje się od źródła, tym mniej prawdopodobne jest, że rozpoznasz ją jako taką i uwzględnisz ją w swoim oszacowaniu. Nagle Twoje szacunkowe 3-4 godziny mogą z łatwością wzrosnąć do 3-4 tygodni. Często ta nieoczekiwana inflacja w harmonogramie ma miejsce jeden lub dwa dni na raz. Nierzadko zdarza się, że "szybkie" refaktoryzacje trwają kilka miesięcy. W takich przypadkach szkody dla wiarygodności i kapitału politycznego odpowiedzialnego zespołu będą się wahać od poważnych do śmiertelnych. Gdybyśmy tylko mieli narzędzie, które pomogłoby nam zidentyfikować i zmierzyć to ryzyko. W rzeczywistości mamy wiele sposobów mierzenia i kontrolowania stopnia i głębokości sprzężenia i złożoności naszego kodu. Metryki oprogramowania mogą służyć do zliczania wystąpień określonych funkcji w naszym kodzie. Wartości tych zliczeń są skorelowane z jakością kodu. Dwie z wielu metryk, które mierzą sprzężenie, to rozszerzanie i rozszerzanie. Rozważ rozłożenie dla klas: jest ono zdefiniowane jako liczba klas, do których odwołuje się bezpośrednio lub pośrednio z klasy będącej przedmiotem zainteresowania. Możesz myśleć o tym jako o liczbie wszystkich klas, które muszą zostać skompilowane, zanim Twoja klasa będzie mogła zostać skompilowana. Z drugiej strony, fan-in to liczba wszystkich klas, które zależą od klasy zainteresowania. Znając rozłożenie i rozłożenie, możemy obliczyć współczynnik niestabilności za pomocą I = fo / (fi + fo). Gdy zbliżam się do 0, pakiet staje się bardziej stabilny. Gdy zbliżam się do 1, pakiet staje się niestabilny. Pakiety, które są stabilne, są celami przekodowywania o niskim ryzyku, podczas gdy pakiety niestabilne są bardziej prawdopodobne, że zostaną wypełnione brudnymi bombami kodowymi. Celem refaktoryzacji jest przybliżenie I do 0. Używając metryk należy pamiętać, że są one tylko regułami humb. Bazując wyłącznie na matematyce, widzimy, że zwiększenie fi bez zmiany fo zbliży I do 0. Istnieje jednak wada bardzo dużej wartości fan-in: te klasy będą trudniejsze do zmiany bez naruszania zależności. Poza tym bez zajmowania się fan-outem tak naprawdę nie zmniejszasz swojego ryzyka, więc musisz zachować równowagę. Jedną z wad metryk oprogramowania jest to, że ogromna liczba liczb generowanych przez narzędzia metryk może być onieśmielająca dla niewtajemniczonych. To powiedziawszy, metryki oprogramowania mogą być potężnym narzędziem w naszej walce o czysty kod. Mogą nam pomóc zidentyfikować i wyeliminować bomby z brudnym kodem, zanim staną się poważnym zagrożeniem dla ćwiczeń dostrajających wydajność.

Prostota pochodzi z redukcji

"Zrób to jeszcze raz w…" - powiedział mi szef, gdy jego palec mocno nacisnął klawisz Delete. Patrzyłem na ekran komputera z aż nazbyt znanym uczuciem zapadania się, gdy mój kod po wierszu znikał w zapomnieniu. Mój szef, Stefan, nie zawsze był najgłośniejszym z ludzi, ale znał zły kod, kiedy go zobaczył. I wiedział dokładnie, co z tym zrobić. Przyszedłem na moją obecną pozycję jako student programista z dużą ilością energii i entuzjazmem, ale absolutnie nie miałem pojęcia, jak kodować. Miałem tę okropną skłonność do myślenia, że rozwiązaniem każdego problemu jest dodanie innej zmiennej w jakimś miejscu. Lub wrzuć kolejną linię. W zły dzień, zamiast poprawiania logiki z każdą poprawką, mój kod stopniowo stawał się większy, bardziej złożony i coraz bardziej oddalał się od spójnego działania. To naturalne, szczególnie gdy się spieszysz, po prostu chcieć dokonać jak najmniejszych zmian w istniejącym bloku kodu, nawet jeśli jest to okropne. Większość programistów zachowa zły kod, obawiając się, że rozpoczęcie od nowa będzie wymagało znacznie więcej wysiłku niż powrót do początku. Może to być prawdą w przypadku kodu, który jest bliski działania, ale jest tylko trochę kodu, który jest poza wszelką pomocą . Więcej czasu marnuje się na próby ratowania złej pracy, niż powinno. Gdy coś staje się pochłaniaczem zasobów, należy je wyrzucić. Szybko. Nie chodzi o to, że należy łatwo wyrzucić całe to pisanie, nazywanie i formatowanie. Reakcja mojego szefa była ekstremalna, ale zmusiła mnie do ponownego przemyślenia kodu przy drugiej (lub czasami trzeciej) próbie. Jednak najlepszym podejściem do naprawy złego kodu jest przejście w tryb, w którym kod jest bezlitośnie refaktoryzowany, przesuwany lub usuwany. Kod powinien być prosty. Powinna istnieć minimalna liczba zmiennych, funkcji, deklaracji i innych potrzeb języka składniowego. Dodatkowe wiersze, dodatkowe zmienne… dodatkowe wszystko, naprawdę, powinno zostać natychmiast usunięte. To, co jest, co zostało, powinno wystarczyć do wykonania zadania, dokończenia algorytmu lub wykonania obliczeń. Wszystko inne to tylko dodatkowy, niechciany hałas, wprowadzony przypadkowo, zasłaniający przepływ i ukrywający ważne rzeczy. Oczywiście, jeśli to nie wystarczy, po prostu usuń to wszystko i wpisz ponownie. Czerpanie z pamięci w ten sposób często może pomóc w przezwyciężeniu wielu niepotrzebnych bałaganów.

Zasada pojedynczej odpowiedzialności

Jedną z najbardziej fundamentalnych zasad dobrego projektowania jest: Zbierz razem te rzeczy, które zmieniają się z tego samego powodu i oddziel te, które zmieniają się z różnych powodów. Zasada ta jest często nazywana zasadą pojedynczej odpowiedzialności lub SRP. W skrócie mówi, że podsystem, moduł, klasa, a nawet funkcja nie powinny mieć więcej niż jednego powodu do zmiany. Klasycznym przykładem jest klasa, która posiada metody, które zajmują się regułami biznesowymi, raportami i bazami danych:

public class Employee {
public Money calculatePay() …
public String reportHours() …
public void save() …
}

Niektórzy programiści mogą pomyśleć, że umieszczenie tych trzech funkcji razem w tej samej klasie jest całkowicie odpowiednie. W końcu klasy mają być zbiorami funkcji operujących na wspólnych zmiennych. Problem polega jednak na tym, że te trzy funkcje zmieniają się z zupełnie innych powodów. Funkcja obliczania płatności zmieni się za każdym razem, gdy zmienią się zasady biznesowe dotyczące obliczania wynagrodzenia. Funkcja reportHours zmieni się, gdy ktoś zechce zmienić format raportu. Funkcja zapisywania zmieni się za każdym razem, gdy administratorzy baz danych zmienią schemat bazy danych. Te trzy powody, dla których warto się zmienić, sprawiają, że Pracownik jest bardzo niestabilny. Zmieni się to z dowolnego z tych powodów. Co ważniejsze, zmiany te będą miały wpływ na wszystkie klasy zależne od pracownika. Dobry projekt systemu oznacza, że dzielimy system na komponenty, które mogą być niezależnie wdrażane. Niezależne wdrażanie oznacza, że jeśli zmienimy jeden komponent, nie musimy ponownie wdrażać żadnego z pozostałych. Jeśli jednak pracownik jest często używany przez wiele innych klas w innych komponentach, każda zmiana w pracowniku prawdopodobnie spowoduje ponowne wdrożenie innych komponentów, co neguje główną korzyść z projektowania komponentów (lub SOA, jeśli wolisz bardziej modną nazwę). . Poniższe proste partycjonowanie rozwiązuje problemy:

public class Employee {
public Money calculatePay() …
}
public class EmployeeReporter {
public String reportHours(Employee e) …
}
public class EmployeeRepository {
public void save(Employee e) …
}

Każdą klasę można umieścić we własnym komponencie. Lub raczej wszystkie klasy sprawozdawcze mogą przejść do komponentu sprawozdawczego. Wszystkie klasy związane z bazą danych mogą przejść do komponentu repozytorium. A wszystkie reguły biznesowe można umieścić w komponencie reguł biznesowych. Wnikliwy czytelnik zauważy, że w powyższym rozwiązaniu nadal istnieją zależności. Ten Pracownik jest nadal zależny od innych klas. Jeśli więc pracownik zostanie zmodyfikowany, inne klasy prawdopodobnie będą musiały zostać ponownie skompilowane i ponownie wdrożone. W związku z tym Pracownik nie może być modyfikowany, a następnie niezależnie wdrażany. Jednak inne klasy można modyfikować i wdrażać niezależnie. Żadna modyfikacja jednego z nich nie może wymusić rekompilacji lub ponownego wdrożenia pozostałych. Nawet Pracownik mógłby zostać wdrożony niezależnie dzięki starannemu wykorzystaniu zasady odwrócenia zależności (DIP), ale to temat na inną książkę. Ostrożne stosowanie SRP, oddzielanie rzeczy, które zmieniają się z różnych powodów, jest jednym z kluczy do tworzenia projektów, które mają niezależnie wdrażaną strukturę komponentów.

Zacznij od Tak

Ostatnio byłam w sklepie spożywczym, szukając wysoko i nisko "edamame" (o którym tylko mgliście wiedziałem, że jest jakimś warzywem). Nie byłam pewna, czy to coś, co znajdę w dziale warzywnym, mrożonym, czy w puszce. Poddałem się i wytropiłem pracownika, który mi pomógł. Ona też nie wiedziała! Pracownik mógł odpowiedzieć na wiele różnych sposobów. Mogła sprawić, że poczuję się ignorantem, że nie wiem, gdzie szukać, albo dać mi niejasne możliwości, a nawet po prostu powiedzieć, że nie mają tego przedmiotu. Zamiast tego potraktowała prośbę jako okazję do znalezienia rozwiązania i pomocy klientowi. Zadzwoniła do innych pracowników i w ciągu kilku minut zaprowadziła mnie do konkretnego przedmiotu, znajdującego się w zamrożonej części. Pracownik w tym przypadku spojrzał na prośbę i wyszedł z założenia, że rozwiążemy problem i spełnimy prośbę. Zaczęła od tak zamiast od nie. Kiedy po raz pierwszy objąłem stanowisko kierownika technicznego, czułem, że moim zadaniem jest ochrona mojego pięknego oprogramowania przed śmiesznym strumieniem żądań ze strony menedżerów produktu i analityków biznesowych. Zacząłem większość rozmów, widząc prośbę jako coś do pokonania, a nie coś do spełnienia. W pewnym momencie doznałem objawienia, że być może istnieje inny sposób pracy, który polega jedynie na zmianie mojej perspektywy z zaczynania od nie na zaczynanie od tak. W rzeczywistości doszedłem do przekonania, że zaczynanie od "tak" jest w rzeczywistości istotną częścią bycia liderem technicznym. Ta prosta zmiana radykalnie zmieniła moje podejście do pracy. Jak się okazuje, jest wiele sposobów na powiedzenie "tak". Kiedy ktoś mówi do ciebie: "Hej, ta aplikacja byłaby naprawdę kolanami pszczół, gdybyśmy zrobili wszystkie okna okrągłe i przezroczyste!", możesz odrzucić to jako śmieszne. Ale często lepiej zacząć od "Dlaczego?" zamiast. Często istnieje jakiś rzeczywisty i przekonujący powód, dla którego ta osoba prosi o okrągłe, półprzezroczyste okna. Na przykład, możesz właśnie podpisać kontrakt z dużym, nowym klientem z komisją normalizacyjną, która nakazuje okrągłe, przezroczyste okna. Zwykle przekonasz się, że kiedy znasz kontekst wniosku, otwierają się nowe możliwości. Często zdarza się, że żądanie jest realizowane w istniejącym produkcie w inny sposób, co pozwala odpowiedzieć "tak" bez żadnego wysiłku: "Właściwie w preferencjach użytkownika możesz pobrać okrągłą, przezroczystą skórkę okna i ją włączyć. " Czasami druga osoba po prostu wpadnie na pomysł, który uznasz za niezgodny z Twoim poglądem na produkt. Uważam, że zwykle pomocne jest odwrócenie tego "Dlaczego?" na siebie. Czasami akt wypowiedzenia przyczyny jasno pokaże, że twoja pierwsza reakcja nie ma sensu. Jeśli nie, być może będziesz musiał podnieść poprzeczkę i sprowadzić innych kluczowych decydentów. Pamiętaj, że celem tego wszystkiego jest powiedzenie "tak" drugiej osobie i staranie się, aby to zadziałało, nie tylko dla niej, ale także dla Ciebie i Twojego zespołu. Jeśli możesz wyrazić przekonujące wyjaśnienie, dlaczego żądanie funkcji jest niezgodne z istniejącym produktem, prawdopodobnie przeprowadzisz owocną rozmowę na temat tego, czy tworzysz właściwy produkt. Niezależnie od tego, jak zakończy się ta rozmowa, wszyscy skupią się bardziej na tym, czym jest produkt, a czym nie. Zaczynanie od tak oznacza pracę z kolegami, a nie przeciwko nim.

Cofnij się i zautomatyzuj, zautomatyzuj, zautomatyzuj

Pracowałem z programistami, którzy poproszeni o wyliczenie wierszy kodu w module, wkleił pliki do edytora tekstu i użyli jego funkcji "liczba wierszy". I zrobili to ponownie w przyszłym tygodniu. I tydzień później. To było złe. Pracowałem nad projektem, który miał kłopotliwy proces wdrażania, polegający na podpisaniu kodu i przeniesieniu wyniku na serwer, co wymagało wielu kliknięć myszką. Ktoś go zautomatyzował, a skrypt uruchamiał się setki razy podczas końcowych testów, znacznie częściej niż oczekiwano. To było dobre. Dlaczego więc ludzie wykonują to samo zadanie w kółko, zamiast cofać się i poświęcać czas na jego automatyzację?

Powszechne błędne przekonanie nr 1: Automatyzacja służy tylko do testowania Jasne, automatyzacja testów jest świetna, ale po co na tym poprzestać? W każdym projekcie obfitują powtarzalne zadania: kontrola wersji, kompilacja, budowanie plików JAR, generowanie dokumentacji, wdrażanie i raportowanie. W przypadku wielu z tych zadań skrypt jest potężniejszy niż mysz. Wykonywanie żmudnych zadań staje się szybsze i bardziej niezawodne.

Powszechne błędne przekonanie nr 2: Mam IDE, więc nie muszę automatyzować kłótnia z kolegami z drużyny? Nowoczesne środowiska IDE mają tysiące potencjalnych ustawień i zasadniczo niemożliwe jest zapewnienie, aby wszyscy członkowie zespołu mieli identyczne konfiguracje. Buduj systemy automatyzacji, takie jak Ant lub Autotools, zapewniają kontrolę i powtarzalność.

Powszechne błędne przekonanie nr 3: Muszę nauczyć się egzotycznych narzędzi, aby zautomatyzować. Z przyzwoitym językiem powłoki (takim jak bash lub Power-Shell) i systemem automatyzacji kompilacji można przejść długą drogę. Jeśli potrzebujesz wejść w interakcję ze stronami internetowymi, użyj narzędzia takiego jak iMacros lub Selenium.

Powszechne błędne przekonanie nr 4: Nie mogę zautomatyzować tego zadania, ponieważ nie mogę poradzić sobie z tymi formatami plików Jeśli część procesu wymaga dokumentów programu Word, arkuszy kalkulacyjnych lub obrazów, automatyzacja może być naprawdę trudna. Ale czy to naprawdę konieczne? Czy możesz używać zwykłego tekstu? Wartości oddzielone przecinkami? XML? Narzędzie generujące rysunek z pliku tekstowego? Często drobne poprawki w procesie mogą przynieść dobre rezultaty przy radykalnym zmniejszeniu uciążliwości.

Powszechne nieporozumienie nr 5: nie mam czasu, aby to rozgryźć. Nie musisz uczyć się całego basha lub Anta, aby zacząć. Ucz się na bieżąco. Kiedy masz zadanie, które Twoim zdaniem może i powinno zostać zautomatyzowane, dowiedz się wystarczająco dużo o swoich narzędziach, aby to zrobić. I rób to na początku projektu, kiedy zwykle łatwiej jest znaleźć czas. Gdy odniesiesz sukces, Ty (i Twój szef) przekonacie się, że warto inwestować w automatyzację.

Skorzystaj z narzędzi do analizy kodu

Wartość testowania jest czymś, co jest wpajane programistom od wczesnych etapów ich podróży programistycznej. W ostatnich latach wzrost liczby testów jednostkowych, rozwoju sterowanego testami i metod zwinnych świadczy o wzroście zainteresowania jak najlepszym wykorzystaniem testów we wszystkich fazach cyklu rozwojowego. Jednak testowanie to tylko jedno z wielu narzędzi, które można wykorzystać do poprawy jakości kodu. W czasach, gdy C było wciąż nowym zjawiskiem, czas procesora i wszelkiego rodzaju pamięć były na wagę złota. Pierwsze kompilatory języka C były tego świadome i ograniczyły liczbę przejść przez kod, usuwając niektóre analizy semantyczne. Oznaczało to, że kompilator sprawdzał tylko mały podzbiór błędów, które można było wykryć w czasie kompilacji. Aby to zrekompensować, Stephen Johnson napisał narzędzie o nazwie lint - które usuwa ten błąd z kodu - które zaimplementowało niektóre statyczne analizy, które zostały usunięte z jego siostrzanego kompilatora C. Narzędzia do analizy statycznej zyskały jednak reputację dającą dużą liczbę fałszywie pozytywnych ostrzeżeń i ostrzeżeń o konwencjach stylistycznych, których nie zawsze trzeba przestrzegać. Obecny krajobraz języków, kompilatorów i narzędzi do analizy statycznej jest bardzo różny. Pamięć i czas procesora są teraz stosunkowo tanie, więc kompilatory mogą sobie pozwolić na sprawdzanie większej liczby błędów. Prawie każdy język może pochwalić się co najmniej jednym narzędziem, które sprawdza, czy nie doszło do naruszeń przewodników stylistycznych, typowych błędów, a czasem sprytnych błędów, które mogą być trudne do wychwycenia, takich jak potencjalne dereferencje wskaźnika zerowego. Bardziej wyrafinowane narzędzia, takie jak Splint dla C lub Pylint dla Pythona, są konfigurowalne, co oznacza, że możesz wybrać, jakie błędy i ostrzeżenia emituje narzędzie za pomocą pliku konfiguracyjnego, za pomocą przełączników wiersza poleceń lub w IDE. Splint pozwoli Ci nawet dodawać adnotacje do kodu w komentarzach, aby dać mu lepsze wskazówki dotyczące działania Twojego programu. Jeśli wszystko inne zawiedzie i zaczniesz szukać prostych błędów lub naruszeń standardów, które nie zostaną wyłapane przez kompilator, IDE lub narzędzia lint, zawsze możesz uruchomić swój własny statyczny kontroler. To nie jest takie trudne, jak mogłoby się wydawać. Większość języków, zwłaszcza tych oznaczonych marką dynamic, udostępnia swoje abstrakcyjne drzewo składni i narzędzia kompilatora jako część swojej standardowej biblioteki. Warto poznać zakurzone zakamarki standardowych bibliotek używanych przez zespół programistów języka, którego używasz, ponieważ często zawierają one ukryte klejnoty przydatne do analizy statycznej i testowania dynamicznego. Na przykład standardowa biblioteka Pythona zawiera deasembler, który podaje kod bajtowy użyty do wygenerowania skompilowanego kodu lub obiektu kodu. Brzmi to jak mało znane narzędzie dla twórców kompilatorów w zespole Python-dev, ale w rzeczywistości jest zaskakująco przydatne w codziennych sytuacjach. Jedną z rzeczy, które ta biblioteka może rozmontować, jest twój ostatni ślad stosu, dający informację zwrotną na temat dokładnie, która instrukcja kodu bajtowego spowodowała ostatni nieprzechwycony wyjątek. Nie pozwól więc, aby testowanie zakończyło kontrolę jakości - skorzystaj z narzędzi analitycznych i nie bój się wprowadzać własnych.

Testuj zachowanie wymagane, a nie przypadkowe

Częstą pułapką w testowaniu jest założenie, że dokładnie to, co robi implementacja, jest dokładnie tym, co chcesz przetestować. Na pierwszy rzut oka brzmi to bardziej jak cnota niż pułapka. Ujmując to inaczej, problem staje się jednak bardziej oczywisty: częstą pułapką w testowaniu jest testowanie sprzętowe do specyfiki implementacji, gdzie te specyfikacje są przypadkowe i nie mają wpływu na pożądaną funkcjonalność. Gdy testy są powiązane z implementacją incydentów, zmiany w implementacji, które są faktycznie kompatybilne z wymaganym zachowaniem, mogą spowodować niepowodzenie testów, co prowadzi do fałszywych alarmów. Programiści zazwyczaj odpowiadają albo poprzez przepisanie testu lub przepisanie kodu. Założenie, że fałszywie pozytywny wynik jest w rzeczywistości prawdziwym pozytywem, jest często konsekwencją strachu, niepewności lub wątpliwości. Skutkuje to podniesieniem statusu zachowania incydentalnego do zachowania wymaganego. Przepisując test, programiści albo ponownie skupiają test na wymaganym zachowaniu (dobre) albo po prostu podłączają go do nowej implementacji (niedobre). Testy muszą być wystarczająco precyzyjne, ale muszą też być dokładne. Na przykład w porównaniu trójstronnym, takim jak String.compareTo w Javie lub strcmp w języku C, wymagania wyniku są takie, że wynik jest ujemny, jeśli lewa strona jest mniejsza od prawej, dodatnia, jeśli lewa strona jest większa od prawej i zero, jeśli są uważane za równe. Ten styl porównywania jest używany w wielu interfejsach API, w tym w komparatorze dla funkcji qsort w języku C i CompareTo w interfejsie Comparable Java. Chociaż konkretne wartości -1 i +1 są powszechnie używane w implementacjach, aby oznaczać odpowiednio mniejsze niż i większe niż, programiści często błędnie zakładają, że wartości te reprezentują rzeczywiste wymagania i w konsekwencji piszą testy, które publicznie potwierdzają to założenie. Podobny problem pojawia się w przypadku testów, które zapewniają odstępy, precyzyjne sformułowania i inne aspekty formatowania i prezentacji tekstu, które są przypadkowe. Jeśli nie piszesz na przykład generatora XML, który oferuje konfigurowalne formatowanie, odstępy nie powinny mieć znaczenia dla wyniku. Podobnie rozmieszczenie przycisków i etykiet na elementach sterujących interfejsu użytkownika ogranicza możliwość zmiany i udoskonalenia tych incydentów w przyszłości. Drobne zmiany w implementacji i nieistotne zmiany w formatowaniu nagle stają się łamaczami kompilacji. Nadmiernie określone testy są często problemem w podejściach whitebox do testów jednostkowych. Testy białoskrzynkowe wykorzystują strukturę kodu do określenia potrzebnych przypadków testowych. Typowy tryb niepowodzenia testowania białej skrzynki polega na tym, że testy się kończą twierdząc, że kod robi to, co robi kod. Proste powtórzenie tego, co już jest oczywiste z kodu, nie dodaje żadnej wartości i prowadzi do fałszywego poczucia postępu i bezpieczeństwa. Aby były skuteczne, testy muszą określać zobowiązania umowne, a nie implementacje papugi. Muszą zrobić czarnoskrzynkowy widok testowanych jednostek, szkicując kontrakty interfejsu w formie wykonywalnej. Dlatego dostosuj testowane zachowanie do wymaganego zachowania.

Testuj dokładnie i konkretnie

Ważne jest, aby przetestować pożądane, podstawowe zachowanie jednostki kodu, a nie przypadkowe zachowanie jej konkretnej implementacji. Ale nie należy tego traktować ani mylić jako wymówki dla niejasnych testów. Testy muszą być zarówno dokładne, jak i precyzyjne. Coś z wypróbowanych, przetestowanych i przetestowanych klasycznych procedur sortowania stanowi ilustracyjny przykład. Implementacja algorytmu sortującego niekoniecznie jest codziennym zadaniem programisty, ale sortowanie jest tak dobrze znanym pomysłem, że większość ludzi wierzy, że wie, czego się po nim spodziewać. Ta swobodna znajomość może jednak utrudnić przejrzenie pewnych założeń. Kiedy programiści są pytani: "Co byś przetestował?", zdecydowanie najczęstszą odpowiedzią jest coś w rodzaju: "Wynikiem sortowania jest posortowana sekwencja elementów". Chociaż to prawda, nie jest to cała prawda. Gdy pojawi się monit o dokładniejszy warunek, wielu programistów dodaje, że wynikowa sekwencja powinna mieć taką samą długość jak oryginał. To prawda, ale to wciąż za mało. Na przykład podając następującą sekwencję:

3 1 4 1 5 9

Następująca sekwencja spełnia warunek posortowania w kolejności nie malejącej i ma taką samą długość jak oryginalna sekwencja:

3 3 3 3 3 3

Chociaż spełnia specyfikację, z pewnością nie o to chodziło! Ten przykład bazuje na błędzie zaczerpniętym z rzeczywistego kodu produkcyjnego (na szczęście wychwyconym przed jego wydaniem), gdzie zwykłe naciśnięcie klawisza lub chwilowa utrata rozsądku doprowadziła do skomplikowanego mechanizmu wypełniania całego wyniku pierwszym elementem podana tablica. Pełny warunek końcowy polega na tym, że wynik jest posortowany i zawiera permutację oryginalnych wartości. To odpowiednio ogranicza wymagane zachowanie. Czy długość wyniku jest taka sama, jak długość wejściowa wyjdzie w praniu i nie wymaga ponownego przerabiania. Nawet określenie stanu końcowego w opisany sposób nie wystarczy, aby dać dobry test. Dobry test powinien być czytelny. Powinna być zrozumiała i na tyle prosta, aby można było łatwo zauważyć, że jest poprawna (lub nie). Jeśli nie masz już kodu sprawdzającego, czy sekwencja jest posortowana i czy jedna sekwencja zawiera permutację wartości w innej, jest całkiem prawdopodobne, że kod testowy będzie bardziej złożony niż kod testowany. Jak zauważył Tony Hoare:

Istnieją dwa sposoby konstruowania projektu oprogramowania: jednym sposobem jest uczynienie go tak prostym, że oczywiście nie ma w nim braków, a drugim jest uczynienie go tak skomplikowanym, że nie ma oczywistych braków.

Korzystanie z konkretnych przykładów eliminuje tę przypadkową złożoność i możliwość wypadku. Na przykład podając następującą sekwencję:

3 1 4 1 5 9

Wynik sortowania jest następujący:

1 1 3 4 5 9

Żadna inna odpowiedź nie wystarczy. Nie akceptuj żadnych substytutów. Konkretne przykłady pomagają zilustrować ogólne zachowanie w przystępny i jednoznaczny sposób. Skutkiem dodania elementu do pustej kolekcji nie jest po prostu to, że nie jest ona pusta: oznacza to, że kolekcja ma teraz pojedynczy element, a pojedynczy przechowywany element jest dodanym elementem. Co najmniej dwie pozycje kwalifikowałyby się jako niepuste, a także byłyby błędne. Pojedynczy przedmiot o innej wartości również byłby błędny. Rezultatem dodania wiersza do tabeli nie jest po prostu to, że tabela jest o jeden wiersz większa; jest to również to, że klucz wiersza może być użyty do odzyskania dodanego wiersza. I tak dalej. Przy określaniu zachowania testy nie powinny być po prostu dokładne: muszą być również precyzyjne.

Testuj podczas snu (i w weekendy)

Zrelaksuj się. Nie mam na myśli centrów rozwoju offshore, nadgodzin w weekendy czy pracy na nocnej zmianie. Chcę raczej zwrócić uwagę na to, jaką mocą obliczeniową dysponujemy. W szczególności, ile nie wykorzystujemy, aby nasze życie jako programistów było trochę łatwiejsze. Czy stale masz trudności z uzyskaniem wystarczającej mocy obliczeniowej w ciągu dnia pracy? Jeśli tak, co robią Twoje serwery testowe poza normalnymi godzinami pracy? Najczęściej serwery testowe są bezczynne przez noc i przez weekend. Możesz to wykorzystać na swoją korzyść.

• o Czy byłeś winny dokonania zmiany bez przeprowadzenia wszystkich testów? Jednym z głównych powodów, dla których programiści nie uruchamiają zestawów testów przed zatwierdzeniem kodu, jest czas, jaki mogą one zająć. Kiedy zbliżają się terminy i zbliżają się naciski, ludzie w naturalny sposób zaczynają iść na skróty. Jednym ze sposobów rozwiązania tego problemu jest rozbicie dużego zestawu testów na dwa lub więcej profili. Mniejszy, obowiązkowy profil testowy, który można szybko uruchomić, pomoże zapewnić uruchamianie testów przed każdym zatwierdzeniem. Wszystkie profile testowe (w tym profil obowiązkowy - dla pewności) można zautomatyzować i uruchomić w nocy, aby rano raportować swoje wyniki.

• o Czy miałeś wystarczająco dużo okazji, aby przetestować stabilność swojego produktu? Dłuższe testy są niezbędne do identyfikacji wycieków pamięci i innych problemów ze stabilnością. Rzadko są uruchamiane w ciągu dnia, ponieważ zabiera to czas i zasoby. Możesz zautomatyzować test zanurzenia, który będzie przeprowadzany w nocy i nieco dłuższy w weekend. Od 18:00 w piątek do 6:00 w następny poniedziałek istnieje 60 godzin potencjalnego czasu testowania.

•  Czy otrzymujesz czas w swoim środowisku testowania wydajności? Widziałem zespoły kłócące się ze sobą, aby uzyskać czas w środowisku testowania wydajności. W większości przypadków żaden zespół nie ma wystarczająco dużo czasu w ciągu dnia, podczas gdy środowisko jest praktycznie bezczynne po godzinach. Serwery i sieć nie są tak zajęte w nocy, ani w weekend. To idealny czas na przeprowadzenie kilku testów jakości.

•  Czy istnieje zbyt wiele permutacji do ręcznego przetestowania? W wielu przypadkach Twój produkt jest przeznaczony do uruchamiania na różnych platformach. Na przykład zarówno 32-bitowe, jak i 64-bitowe, w systemach Linux, Solaris i Windows lub po prostu w różnych wersjach tego samego systemu operacyjnego. Co gorsza, wiele nowoczesnych aplikacji naraża się na działanie wielu mechanizmów i protokołów transportowych (HTTP, AMQP, SOAP, CORBA itp.). Ręczne testowanie wszystkich tych permutacji jest bardzo czasochłonne i najprawdopodobniej wykonywane blisko wydania z powodu presji zasobów. Niestety, może być za późno w cyklu, aby złapać niektóre paskudne błędy.

Zautomatyzowane testy przeprowadzane w nocy lub w weekendy zapewnią częstsze testowanie wszystkich tych permutacji. Przy odrobinie myślenia i wiedzy na temat skryptów możesz zaplanować kilka zadań crona, aby rozpocząć testowanie w nocy i w weekend. Istnieje również wiele narzędzi testowych, które mogą pomóc. Niektóre organizacje mają nawet sieci serwerów, które łączą serwery z różnych działów i zespołów, aby zapewnić efektywne wykorzystanie zasobów. Jeśli jest to dostępne w Twojej organizacji, możesz przesłać testy, które będą przeprowadzane w nocy lub w weekendy.

Testowanie to inżynierski rygor tworzenia oprogramowania

Deweloperzy uwielbiają używać torturowanych metafor, gdy próbują wyjaśnić, co robią członkom rodziny, małżonkom i innym nontechom. Często uciekamy się do budowy mostów i innych "twardych" dyscyplin inżynieryjnych. Wszystkie te metafory szybko się jednak gubią, kiedy zaczynasz je zbyt mocno naciskać. Okazuje się, że tworzenie oprogramowania nie przypomina wielu "twardych" dyscyplin inżynieryjnych pod wieloma ważnymi względami. W porównaniu z "twardą" inżynierią, świat tworzenia oprogramowania jest mniej więcej w tym samym miejscu, w którym znajdowali się budowniczowie mostów, gdy powszechną strategią było zbudowanie mostu, a następnie przerzucenie po nim czegoś ciężkiego. Jeśli został, to był dobry most. Jeśli nie, to czas wrócić do deski kreślarskiej. W ciągu ostatnich kilku tysięcy lat inżynierowie opracowali matematykę i fizykę, których mogą użyć do rozwiązania konstrukcyjnego bez konieczności budowania go, aby zobaczyć, co robi. Nie mamy czegoś takiego w oprogramowaniu i być może nigdy nie będziemy mieć, ponieważ oprogramowanie jest w rzeczywistości bardzo różne. Aby dogłębnie zbadać porównanie między "inżynierią" oprogramowania a zwykłą inżynierią, "Czym jest projektowanie oprogramowania?", napisane przez Jacka Reevesa w C++ Journal w 1992 roku, jest klasykiem. Mimo że został napisany prawie dwie dekady temu, nadal jest niezwykle dokładny. Reeves nakreślił w tym porównaniu ponury obraz, ale to, czego brakowało w 1992 roku, to silny etos testowania oprogramowania. Testowanie "twardych" rzeczy jest trudne, ponieważ musisz je zbudować, aby je przetestować, co zniechęca do spekulacji, aby zobaczyć, co się stanie. Ale proces budowania oprogramowania jest śmiesznie tani. Opracowaliśmy cały ekosystem narzędzi, które to ułatwiają: testowanie jednostkowe, makiety obiektów, wiązki testowe i wiele innych rzeczy. Inni inżynierowie chcieliby móc coś zbudować i przetestować w realistycznych warunkach. Jako twórcy oprogramowania powinniśmy przyjąć testowanie jako podstawowy (ale nie jedyny) mechanizm weryfikacji oprogramowania. Zamiast czekać na jakiś rachunek dla oprogramowania, mamy już do dyspozycji narzędzia zapewniające dobre praktyki inżynierskie. Patrząc w tym świetle, mamy teraz amunicję przeciwko menedżerom, którzy mówią nam "nie mamy czasu na testy". Budowniczy mostów nigdy nie usłyszałby od swojego szefa: "Nie zawracaj sobie głowy analizą strukturalną tego budynku - mamy napięty termin". Uznanie, że testowanie jest rzeczywiście ścieżką do odtwarzalności i jakości oprogramowania, pozwala nam, jako programistom, odpierać argumenty przeciwko niemu jako profesjonalnie nieodpowiedzialnemu. Testowanie wymaga czasu, podobnie jak analiza konstrukcji wymaga czasu. Obie czynności zapewniają jakość produktu końcowego. Czas, aby twórcy oprogramowania przejęli odpowiedzialność za to, co produkują. Samo testowanie nie wystarczy, ale jest konieczne. Testowanie to inżynierski rygor tworzenia oprogramowania

Myślenie w stanach

Ludzie w prawdziwym świecie mają dziwny związek ze stanami. Dziś rano wpadłem do lokalnego sklepu, aby przygotować się na kolejny dzień konwersji kofeiny na kod. Ponieważ moim ulubionym sposobem na to jest picie latte, a nie mogłem znaleźć mleka, zapytałem sprzedawcę. "Przepraszamy, jesteśmy super-głupi, mega-brak mleka". Dla programisty to dziwne stwierdzenie. Albo nie masz mleka, albo masz. Nie ma wagi, jeśli chodzi o brak mleka. Może próbowała mi powiedzieć, że przez tydzień nie będzie mleka, ale dla mnie rezultatem był dzień tego samego espresso. W większości rzeczywistych sytuacji zrelaksowany stosunek ludzi do państwa nie jest problemem. Niestety, wielu programistów też ma dość ogólnikowy stan - i to jest problem. Rozważ prosty sklep internetowy, który akceptuje tylko karty kredytowe i nie wystawia faktur klientom, z klasą Order zawierającą tę metodę:

public boolean isComplete() {
return isPaid() && hasShipipped();
}

Rozsądne, prawda? Cóż, nawet jeśli wyrażenie jest ładnie wyodrębnione do metody zamiast wszędzie kopiować i wklejać, wyrażenie w ogóle nie powinno istnieć. Fakt, że tak, podkreśla problem. Czemu? Ponieważ zamówienie nie może zostać wysłane przed opłaceniem. W związku z tym hasShipped nie może być prawdziwe, chyba że isPaid jest prawdziwe, co sprawia, że część wyrażenia jest zbędna. Możesz nadal chcieć isComplete dla przejrzystości kodu, ale wtedy powinno wyglądać tak:

public boolean isComplete() {
return hasWysłane();
}

W swojej pracy cały czas widzę zarówno brakujące czeki, jak i nadmiarowe czeki. Ten przykład jest niewielki, ale gdy dodasz anulowanie i spłatę, stanie się to bardziej złożone i wzrośnie potrzeba dobrej obsługi stanu. W takim przypadku zamówienie może znajdować się tylko w jednym z trzech różnych stanów:

•  W toku: Można dodawać lub usuwać elementy. Nie można wysłać.
•  Płatne: nie można dodawać ani usuwać elementów. Może być wysłany.
•  Wysłano: Gotowe. Żadne zmiany nie zostały zaakceptowane.

Te stany są ważne i musisz sprawdzić, czy jesteś w oczekiwanym stanie przed wykonaniem operacji, i że przechodzisz tylko do stanu prawnego, z którego jesteś. Krótko mówiąc, musisz starannie chronić swoje przedmioty we właściwych miejscach. Ale jak zacząć myśleć w stanach? Wyodrębnianie wyrażeń do sensownych metod to bardzo dobry początek, ale to dopiero początek. Podstawą jest zrozumienie maszyn stanowych. Wiem, że możesz mieć złe wspomnienia z zajęć CS, ale zostaw je w tyle. Maszyny państwowe nie są szczególnie trudne. Wizualizuj je, aby były łatwe do zrozumienia i łatwe do omówienia. Przetestuj swój kod, aby odkryć prawidłowe i nieprawidłowe stany i przejścia oraz zachować ich poprawność. Przestudiuj wzorzec Stan. Kiedy poczujesz się komfortowo, przeczytaj Design by Contract. Pomaga zapewnić poprawny stan, sprawdzając przychodzące dane i sam obiekt przy wejściu i wyjściu każdej metody publicznej. Jeśli twój stan jest niepoprawny, jest błąd i ryzykujesz zniszczenie danych, jeśli nie przerwiesz. Jeśli uznasz, że kontrole stanu są szumem, dowiedz się, jak używać narzędzia, generowania kodu, tkania lub aspektów, aby je ukryć. Bez względu na to, które podejście wybierzesz, myślenie w stanach sprawi, że Twój kod będzie prostszy i bardziej niezawodny.

Dwie głowy są często lepsze niż jedna

Programowanie wymaga głębokiego myślenia, a głębokie myślenie wymaga samotności. Tak jest ze stereotypem programisty. To podejście "samotnego wilka" do programowania ustępowało miejsca bardziej opartemu na współpracy podejściu, które, jak sądzę, poprawia jakość, produktywność i satysfakcję z pracy programistów. Dzięki takiemu podejściu programiści ściślej współpracują ze sobą, a także z osobami, które nie są programistami - analitykami biznesowymi i systemowymi, specjalistami ds. zapewniania jakości i użytkownikami. Co to oznacza dla programistów? Bycie ekspertem w dziedzinie technologii już nie wystarcza. Musisz być skuteczny w pracy z innymi. Współpraca nie polega na zadawaniu pytań i odpowiadaniu na nie czy siedzeniu na spotkaniach. Chodzi o zakasanie rękawów z kimś innym, aby wspólnie zaatakować pracę. Jestem wielkim fanem programowania w parach. Możesz nazwać to "ekstremalną współpracą". Jako programista moje umiejętności rosną, gdy paruję. Jeśli jestem słabszy niż mój partner w parowaniu w dziedzinie lub technologii, wyraźnie uczę się z jego doświadczenia. Kiedy jestem silniejszy w jakimś aspekcie, dowiaduję się więcej o tym, co wiem, a czego nie, poprzez konieczność wyjaśniania się. Niezmiennie oboje przynosimy coś do stołu i uczymy się od siebie. Podczas parowania każdy z nas wnosi nasze wspólne doświadczenia programistyczne - domenowe i techniczne - do danego problemu i może wnieść unikalny wgląd i doświadczenie w pisanie oprogramowania skutecznie i wydajnie. Nawet w przypadku skrajnej nierównowagi w zakresie wiedzy dziedzinowej lub technicznej, bardziej doświadczony uczestnik niezmiennie uczy się czegoś od drugiego - być może nowego skrótu klawiaturowego lub kontaktu z nowym narzędziem lub biblioteką. Dla mniej doświadczonego członka pary jest to świetny sposób, aby przyspieszyć. Programowanie w parach jest popularne, choć nie wyłącznie, wśród zwolenników zwinnego tworzenia oprogramowania. Niektórzy, którzy sprzeciwiają się parowaniu, pytają: "Dlaczego mam płacić dwóm programistom za wykonanie pracy jednego?" Moja odpowiedź brzmi, że rzeczywiście nie powinieneś. Twierdzę, że parowanie zwiększa jakość, zrozumienie domeny i technologii oraz technik (takich jak sztuczki IDE) i łagodzi wpływ ryzyka loterii (jeden z twoich ekspertów programistów wygrywa loterię i rezygnuje następnego dnia). Jaka jest długoterminowa wartość nauki nowego skrótu klawiaturowego? Jak mierzymy ogólną poprawę jakości produktu wynikającą z parowania? Jak mierzymy wpływ twojego partnera, nie pozwalając ci podążać ślepym podejściem do rozwiązania trudnego problemu? Jedno z badań wskazuje na 40% wzrost skuteczności i szybkości.* Jaka jest wartość ograniczania "ryzyka loterii"? Większość z tych korzyści jest trudna do zmierzenia. Kto powinien z kim się łączyć? Jeśli jesteś nowy w zespole, ważne jest, aby znaleźć członka zespołu, który ma wiedzę. Równie ważne jest znalezienie kogoś, kto ma dobre umiejętności interpersonalne i coachingowe. Jeśli nie masz dużego doświadczenia w domenie, połącz się z członkiem zespołu, który jest ekspertem w tej domenie. Jeśli nie jesteś przekonany, eksperymentuj: współpracuj z kolegami. Połącz interesujący, szorstki problem. Zobacz, jak to jest. Spróbuj kilka razy.

Dwa błędy mogą naprawić (i są trudne do naprawienia)

Kod nigdy nie kłamie, ale może zaprzeczać samemu sobie. Niektóre sprzeczności prowadzą do tego "Jak to może działać?" chwile. W wywiadzie główny projektant oprogramowania Apollo 11 Lunar Module, Allan Klumpp, ujawnił, że oprogramowanie kontrolujące silniki zawierało błąd, który powinien spowodować niestabilność lądownika. Jednak inny błąd zrekompensował pierwszy, a oprogramowanie było używane zarówno do lądowań na Księżycu Apollo 11, jak i 12, zanim jakikolwiek błąd został znaleziony lub naprawiony. Rozważ funkcję, która zwraca stan ukończenia. Wyobraź sobie, że zwraca fałsz, kiedy powinien zwrócić prawdę. Teraz wyobraź sobie, że funkcja wywołująca zaniedbuje sprawdzenie wartości zwracanej. Wszystko działa dobrze, aż pewnego dnia ktoś zauważy brakujący czek i wstawi go. Lub rozważ aplikację, która przechowuje stan jako dokument XML. Wyobraź sobie, że jeden z węzłów jest niepoprawnie zapisany jako TimeToLive zamiast TimeToDie, jak mówi dokumentacja. Wszystko wygląda dobrze, podczas gdy kod pisarza i kod czytnika zawierają ten sam błąd. Ale napraw jeden lub dodaj nową aplikację czytającą ten sam dokument, a symetria jest zepsuta, podobnie jak kod. Kiedy dwie defekty w kodzie tworzą jedną widoczną usterkę, metodyczne podejście do ich naprawy może samo w sobie się załamać. Deweloper otrzymuje raport o błędzie, znajduje usterkę, naprawia ją i ponownie testuje. Zgłoszona usterka jednak nadal występuje, ponieważ działa druga usterka. Tak więc pierwsza poprawka jest usuwana, kod sprawdzany do momentu znalezienia drugiej podstawowej defektu i zastosowana w tym celu poprawka. Ale pierwsza defekt powróciła, zgłoszony błąd jest nadal widoczny, więc druga poprawka jest wycofywana. Proces się powtarza, ale teraz deweloper odrzucił dwie możliwe poprawki i chce wprowadzić trzecią, która nigdy nie zadziała. Wzajemne oddziaływanie dwóch defektów kodu, które pojawiają się jako jeden widoczny błąd, nie tylko utrudnia naprawienie problemu, ale także prowadzi programistów w ślepe zaułki, tylko po to, by na wczesnym etapie wypróbować właściwe odpowiedzi. Nie dzieje się tak tylko w kodzie: problem istnieje również w pisemnych dokumentach wymagań. I może rozprzestrzeniać się wirusowo z jednego miejsca do drugiego. Błąd w kodzie kompensuje błąd w opisie pisemnym. Może również rozprzestrzeniać się na ludzi: użytkownicy dowiadują się, że kiedy aplikacja mówi "lewo", oznacza to "prawo", więc odpowiednio dostosowują swoje zachowanie. Przekazują je nawet nowym użytkownikom: "Pamiętaj, że kiedy aplikacja mówi "kliknij lewym przyciskiem", tak naprawdę oznacza to przycisk po prawej stronie". Napraw błąd i nagle użytkownicy potrzebują przeszkolenia. Pojedyncze błędy mogą być łatwe do zauważenia i łatwe do naprawienia. To problemy z wieloma przyczynami, wymagające wielu zmian, są trudniejsze do rozwiązania. Po części dzieje się tak dlatego, że łatwe problemy są tak łatwe do naprawienia, że ludzie mają tendencję do naprawiania ich stosunkowo szybko i przechowywania trudniejszych problemów na później. Nie ma prostej rady, jak radzić sobie z błędami wynikającymi z defektów współczulnych. Potrzebna jest świadomość możliwości, trzeźwość umysłu i chęć rozważenia wszystkich możliwości.

Kodowanie Ubuntu dla Twoich przyjaciół

Tak często piszemy kod w izolacji i ten kod odzwierciedla naszą osobistą interpretację problemu, a także bardzo spersonalizowane rozwiązanie. Możemy być częścią zespołu, ale jesteśmy odizolowani, podobnie jak zespół. Zbyt łatwo zapominamy, że ten kod stworzony w izolacji będzie wykonywany, używany, rozszerzany i będzie polegał na innych. Łatwo przeoczyć społeczną stronę tworzenia oprogramowania. Tworzenie oprogramowania to ćwiczenie techniczne połączone z ćwiczeniem społecznym. Po prostu musimy częściej podnosić głowy, by zdać sobie sprawę, że nie pracujemy w izolacji i ponosimy wspólną odpowiedzialność za zwiększanie prawdopodobieństwa sukcesu dla wszystkich, a nie tylko dla zespołu programistów. Możesz pisać dobrej jakości kod w odosobnieniu, cały czas zatracając się w sobie. Z jednej perspektywy jest to podejście egocentryczne (nie ego jako aroganckie, ale ego jako osobiste). To także widok Zen i dotyczy Ciebie w momencie tworzenia kodu. Zawsze staram się żyć chwilą, bo to pomaga mi zbliżyć się do dobrej jakości, ale potem żyję chwilą. A co z chwilą mojego zespołu? Czy moja chwila jest taka sama jak chwila zespołu? W Zulu filozofia Ubuntu jest podsumowana jako "Umuntu ngumuntu ngabantu", co z grubsza tłumaczy się jako "Osoba jest osobą poprzez (inne) osoby". Staję się lepszy, ponieważ dzięki swoim dobrym uczynkom czynisz mnie lepszym. Drugą stroną jest to, że pogarszasz się w tym, co robisz, kiedy jestem zły w tym, co robię. Wśród programistów możemy zawęzić je do "Deweloper to programista poprzez (innych) programistów". Jeśli sprowadzimy to do sedna, wtedy "Kod jest kodem poprzez (inny) kod". Jakość kodu, który piszę, wpływa na jakość kodu, który piszesz. Co zrobić, jeśli mój kod jest słabej jakości? Nawet jeśli piszesz bardzo czysty kod, to właśnie w punktach, w których używasz mojego kodu, jakość twojego kodu spadnie do jakości zbliżonej do mojego kodu. Możesz zastosować wiele wzorów i technik, aby ograniczyć obrażenia, ale szkody już zostały wyrządzone. Sprawiłem, że zrobiłeś więcej, niż musiałeś zrobić, po prostu dlatego, że nie myślałem o tobie, kiedy żyłem w swoim czasie. Może uważam, że mój kod jest czysty, ale wciąż mogę go ulepszyć tylko przez kodowanie Ubuntu. Jak wygląda kod Ubuntu? Wygląda jak dobry, czysty kod. Nie chodzi o kod, artefakt. Chodzi o akt tworzenia tego artefaktu. Programowanie dla znajomych w Ubuntu pomoże Twojemu zespołowi żyć Twoimi wartościami i wzmocnić Twoje zasady. Następna osoba, która w jakikolwiek sposób dotknie twojego kodu, będzie lepszą osobą i lepszym programistą. Zen dotyczy jednostki. Ubuntu to temat Zen dla grupy ludzi. Bardzo, bardzo rzadko tworzymy kod tylko dla siebie.

Narzędzia Uniksa to Twoi przyjaciele

Jeśli w drodze na wygnanie na bezludną wyspę musiałbym wybierać między IDE a skrzynką uniksową, wybrałbym narzędzia uniksowe bez namysłu. Oto powody, dla których powinieneś nabrać biegłości w posługiwaniu się narzędziami uniksowymi. Po pierwsze, IDE są ukierunkowane na określone języki, podczas gdy narzędzia uniksowe mogą pracować ze wszystkim, co pojawia się w formie tekstowej. W dzisiejszym środowisku programistycznym, w którym co roku pojawiają się nowe języki i notacje, nauka pracy w sposób uniksowy jest inwestycją, która zwróci się za każdym razem. Co więcej, podczas gdy IDE oferują tylko polecenia wymyślone przez ich programistów, za pomocą narzędzi uniksowych możesz wykonać dowolne zadanie, jakie możesz sobie wyobrazić. Pomyśl o nich jako o (klasycznych pre-Bionicle) klockach Lego: tworzysz własne polecenia po prostu łącząc małe, ale wszechstronne narzędzia uniksowe. Na przykład następująca sekwencja to tekstowa implementacja analizy sygnatur Cunninghama - sekwencja średników, nawiasów klamrowych i cudzysłowów każdego pliku, która może wiele powiedzieć o zawartości pliku:

for i in *.java; do
echo -n "$i: "
sed 's/[^"{};]//g' $i | tr -d '\n'
echo
done

Ponadto każda poznana operacja IDE jest specyficzna dla danego zadania - na przykład dodanie nowego kroku w konfiguracji kompilacji debugowania projektu. W przeciwieństwie do tego, wyostrzenie swoich umiejętności w zakresie narzędzi Unix sprawia, że jesteś bardziej efektywny w każdym zadaniu. Jako przykład użyłem narzędzia sed użytego w poprzedniej sekwencji poleceń, aby zmienić kompilację projektu w celu kompilacji krzyżowej na wielu architekturach procesorów. Narzędzia uniksowe zostały opracowane w czasach, gdy komputer z wieloma użytkownikami miał 128 KB pamięci RAM. Pomysłowość włożona w ich projekt oznacza, że w dzisiejszych czasach potrafią niezwykle wydajnie obsługiwać ogromne zbiory danych. Większość narzędzi działa jak filtry, przetwarzając jednocześnie tylko jeden wiersz, co oznacza, że nie ma górnego limitu ilości danych, które mogą obsłużyć. Chcesz wyszukać liczbę edycji przechowywanych w półterabajtowym zrzutu angielskiej Wikipedii? Proste wywołanie grep '' | wc - dam ci odpowiedź bez potu. Jeśli uznasz, że sekwencja poleceń jest ogólnie przydatna, możesz łatwo spakować ją do skryptu powłoki, używając wyjątkowo potężnych konstrukcji programistycznych, takich jak przesyłanie danych do pętli i instrukcji warunkowych. Co jeszcze bardziej imponujące, polecenia Uniksa wykonywane jako potoki, tak jak poprzednie, w naturalny sposób rozłożą swoje obciążenie na wiele jednostek przetwarzania nowoczesnych procesorów wielordzeniowych. Małe jest piękne pochodzenie i implementacje open source narzędzi uniksowych sprawiają, że są one wszechobecne, nawet na platformach o ograniczonych zasobach, takich jak mój odtwarzacz multimedialny lub router DSL. Takie urządzenia raczej nie oferują potężnego graficznego interfejsu użytkownika, ale często zawierają aplikację BusyBox, która zapewnia najczęściej używane narzędzia. A jeśli programujesz w systemie Windows, środowisko Cygwin oferuje wszystkie możliwe do wyobrażenia narzędzia uniksowe, zarówno w postaci plików wykonywalnych, jak i kodu źródłowego. Wreszcie, jeśli żadne z dostępnych narzędzi nie odpowiada Twoim potrzebom, bardzo łatwo jest rozszerzyć świat narzędzi uniksowych. Po prostu napisz program (w dowolnym języku), który działa według kilku prostych zasad: Twój program powinien wykonać tylko jedno zadanie; powinien czytać dane jako linie tekstu ze swojego standardowego wejścia; i powinien wyświetlać swoje wyniki bez nagłówków i innych szumów na swoim standardowym wyjściu. Parametry wpływające na pracę narzędzia podane są w wierszu poleceń. Przestrzegaj tych zasad, a "Twoja jest Ziemia i wszystko, co na niej jest".

Użyj odpowiedniego algorytmu i struktury danych

Duży bank z wieloma oddziałami skarżył się, że nowe komputery, które kupił dla kasjerów, są zbyt wolne. Było to w czasach, zanim wszyscy korzystali z bankowości elektronicznej, a bankomaty nie były tak rozpowszechnione jak obecnie. Ludzie dużo częściej odwiedzali bank, a wolne komputery powodowały, że ludzie ustawiali się w kolejkach. W konsekwencji bank zagroził zerwaniem umowy ze sprzedawcą. Sprzedawca wysłał specjalistę od analizy wydajności i tuningu, aby ustalić przyczynę opóźnień. Wkrótce znalazł jeden konkretny program działający na terminalu, który zużywał prawie całą pojemność procesora. Używając narzędzia do profilowania, przybliżył program i zobaczył funkcję, która była sprawcą. Kod źródłowy brzmiał:

for (i=0; i if (… s[i] …) …
}

A string s miał średnio tysiące znaków. Kod (napisany przez bank) został szybko zmieniony, a kasjerzy żyli długo i szczęśliwie … Czy programista nie powinien zrobić czegoś lepszego, niż używać kodu niepotrzebnie skalowanego kwadratowo? Każde wywołanie strlen przeszło każdy z wielu tysięcy znaków w ciągu, aby znaleźć kończący go znak null. Ciąg jednak nigdy się nie zmienił. Określając z góry jego długość, programista mógł zaoszczędzić tysiące wywołań strlen (i miliony wykonań pętli):

n=strlen(s);
for (i=0; i if (… s[i] …) …
}
Wszyscy znają powiedzenie "najpierw spraw, aby działało, a następnie spraw, aby działało szybko", aby uniknąć pułapek mikrooptymalizacji. Ale powyższy przykład prawie sprawiłby, że uwierzysz, że programista postępował zgodnie z makiawelicznym adagio "najpierw spraw, aby działało powoli". Ta bezmyślność jest czymś, z czym możesz się spotkać więcej niż raz. I nie chodzi tylko o "nie wymyślanie koła na nowo". Czasami początkujący programiści po prostu zaczynają pisać bez zastanowienia i nagle "wynaleźli" sortowanie bąbelkowe. Może nawet się tym chwalą. Drugą stroną wyboru odpowiedniego algorytmu jest wybór struktury danych. Może to zrobić dużą różnicę: użycie połączonej listy dla kolekcji miliona elementów, które chcesz przeszukiwać - w porównaniu do zaszyfrowanej struktury danych lub drzewa binarnego - będzie miało duży wpływ na ocenę Twojego programowania przez użytkownika.Programiści nie powinni wymyślać koła na nowo i powinni korzystać z istniejących bibliotek tam, gdzie to możliwe. Ale aby móc uniknąć problemów takich jak bank, powinni również być edukowani w zakresie algorytmów i ich skalowania. Czy to tylko słodycz dla oczu w nowoczesnych edytorach tekstu sprawia, że są tak powolne, jak stare programy, takie jak WordStar w latach 80.? Wielu twierdzi, że ponowne wykorzystanie w programowaniu jest najważniejsze. Przede wszystkim jednak programiści powinni wiedzieć, kiedy, co i jak wykorzystać ponownie. W tym celu powinni posiadać wiedzę z zakresu problematyki oraz algorytmów i struktur danych. Dobry programista powinien również wiedzieć, kiedy użyć odrażającego algorytmu. Na przykład, jeśli problematyczna domena mówi, że nigdy nie może być więcej niż pięć przedmiotów (jak liczba kości w grze Yahtzee), wiesz, że zawsze musisz posortować najwyżej pięć przedmiotów. W takim przypadku sortowanie bąbelkowe może być najskuteczniejszym sposobem sortowania elementów. Każdy pies ma swój dzień. Przeczytaj więc kilka dobrych książek - i upewnij się, że je rozumiesz. A jeśli naprawdę przeczytasz The Art of Computer Programming Donalda Knutha, cóż, możesz nawet mieć szczęście: znajdź błąd autora, a otrzymasz jeden z czeków Dona Knutha w systemie szesnastkowym (2,56 USD).

Szczegółowe rejestrowanie zakłóci Twój sen

Kiedy spotykam system, który już od jakiegoś czasu jest w fazie rozwoju lub produkcji, pierwszą oznaką prawdziwych problemów jest zawsze brudny dziennik. Wiesz, o czym mówię: kliknięcie pojedynczego linku w normalnym przepływie na stronie internetowej powoduje zalew wiadomości w jedynym logu, jaki udostępnia system. Zbyt duże rejestrowanie może być równie bezużyteczne, jak żadne. Jeśli twoje systemy są takie jak moje, kiedy twoja praca jest skończona, praca kogoś innego dopiero się zaczyna. Po opracowaniu systemu miejmy nadzieję, że będzie żył długo i dostatnie życie obsługujące klientów (jeśli masz szczęście). Skąd będziesz wiedzieć, że coś pójdzie nie tak, gdy system jest w produkcji i jak sobie z tym poradzisz? Może ktoś monitoruje Twój system za Ciebie, a może sam go monitorujesz. Tak czy inaczej, logi będą prawdopodobnie częścią monitoringu. Jeśli coś się pojawi i musisz się obudzić, aby sobie z tym poradzić, chcesz się upewnić, że jest ku temu dobry powód. Jeśli mój system umiera, chcę wiedzieć. Ale jeśli jest tylko czkawka, wolę cieszyć się moim pięknem snu. W przypadku wielu systemów pierwszą wskazówką, że coś jest nie tak, jest komunikat dziennika zapisywany w jakimś dzienniku. Przeważnie będzie to dziennik błędów. Więc zrób sobie przysługę: od pierwszego dnia upewnij się, że jeśli coś jest zarejestrowane w dzienniku błędów, chcesz, aby ktoś zadzwonił i obudził Cię w środku nocy. Jeśli możesz zasymulować obciążenie systemu podczas testowania systemu, przeglądanie bezszumowego dziennika błędów jest również dobrą pierwszą wskazówką, że system jest dość solidny - lub wczesne ostrzeżenie, jeśli nie jest to system rozproszony. Systemy rozproszone dodają kolejny poziom złożoności. Musisz zdecydować, jak radzić sobie z awarią zewnętrznej zależności. Jeśli twój system jest bardzo rozproszony, może to być częstym zjawiskiem. Upewnij się, że Twoje zasady logowania uwzględniają to. Ogólnie rzecz biorąc, najlepszą wskazówką, że wszystko jest w porządku, jest to, że wiadomości o niższym priorytecie szczęśliwie tykają. Potrzebuję około jednego komunikatu dziennika na poziomie INFO dla każdego znaczącego zdarzenia aplikacji. Zaśmiecony dziennik wskazuje, że system będzie trudny do kontrolowania po wejściu do produkcji. Jeśli nie spodziewasz się, że coś pojawi się w dzienniku błędów, znacznie łatwiej będzie wiedzieć, co zrobić, gdy coś się pojawi.

WET osłabia wąskie gardła w wydajności

Znaczenie zasady DRY (Don′t Repeat Yourself) polega na tym, że kodyfikuje ideę, że każda wiedza w systemie powinna mieć pojedynczą reprezentację. Innymi słowy, wiedza powinna być zawarta w jednej implementacji. Przeciwieństwem DRY jest WET (Write Every Time). Nasz kod jest WET, gdy wiedza jest skodyfikowana w kilku różnych implementacjach. Konsekwencje dotyczące wydajności SUCHY i MOKRY stają się bardzo jasne, gdy weźmie się pod uwagę ich liczne wpływy na profil wydajności. Zacznijmy od rozważenia funkcji naszego systemu, powiedzmy X, czyli wąskiego gardła procesora. Powiedzmy, że funkcja X zużywa 30% procesora. Załóżmy teraz, że funkcja X ma 10 różnych implementacji. Każda implementacja będzie zużywać średnio 3% procesora. Ponieważ ten poziom wykorzystania procesora nie jest wart zamartwiania się, jeśli szukamy szybkiej wygranej, prawdopodobnie nie zauważylibyśmy, że ta funkcja jest naszym wąskim gardłem. Załóżmy jednak, że w jakiś sposób rozpoznaliśmy funkcję X jako wąskie gardło. Pozostaje nam teraz problem ze znalezieniem i naprawą każdej pojedynczej implementacji. W WET mamy 10 różnych implementacji, które musimy znaleźć i naprawić. Dzięki DRY wyraźnie zobaczylibyśmy 30% wykorzystanie procesora i musielibyśmy naprawić jedną dziesiątą kodu. A czy wspomniałem, że nie musimy tracić czasu na szukanie każdej implementacji? Jest jeden przypadek użycia, w którym często jesteśmy winni naruszenia DRY: nasze wykorzystanie kolekcji. Powszechną techniką implementacji zapytania jest iteracja kolekcji, a następnie zastosowanie zapytania po kolei do każdego elementu:

public class UsageExample {
private ArrayList allCustomers = new ArrayList();
// …
public ArrayList findCustomersThatSpendAtLeast(Money amount) {
ArrayList customersOfInterest = new ArrayList();
for (Customer customer: allCustomers) {
if (customer.spendsAtLeast(amount))
customersOfInterest.add(customer);
}
return customersOfInterest;
}
}

Ujawniając tę surową kolekcję klientom, naruszyliśmy zasady enkapsulacji. To nie tylko ogranicza naszą zdolność do refaktoryzacji, ale także zmusza użytkowników naszego kodu do naruszania DRY poprzez ponowne zaimplementowanie przez każdego z nich potencjalnie tego samego zapytania. Sytuacji tej można łatwo uniknąć, usuwając ujawnione kolekcje surowe z interfejsu API. W tym przykładzie możemy wprowadzić nowy, specyficzny dla domeny typ zbiorowy o nazwie CustomerList. Ta nowa klasa jest bardziej zgodna semantycznie z naszą domeną. Będzie działać jako naturalny dom dla wszystkich naszych zapytań. Posiadanie tego nowego typu kolekcji pozwoli nam również łatwo sprawdzić, czy te zapytania są wąskim gardłem wydajności. Włączając zapytania do klasy, eliminujemy potrzebę udostępniania naszym klientom opcji reprezentacji, takich jak ArrayList. Daje nam to swobodę zmiany tych wdrożeń bez obaw o naruszenie umów z klientami:

public class CustomerList {
private ArrayList customers = new ArrayList();
private SortedList customersSortedBySpendingLevel =
new SortedList // …
public CustomerList findCustomersThatSpendAtLeast(Money amount) {
return new CustomerList(
customersSortedBySpendingLevel.elementsLargerThan(amount));
}
}
public class UsageExample {
public static void main(String[] args) {
CustomerList customers = new CustomerList();
// …
CustomerList customersOfInterest =
customers.findCustomersThatSpendAtLeast(someMinimalAmount);
// …
}
}

W tym przykładzie przestrzeganie DRY pozwoliło nam wprowadzić alternatywny schemat indeksowania z SortedList kluczem do poziomu wydatków naszych klientów. Ważniejsze niż szczegółowe informacje w tym konkretnym przykładzie, po DRY pomogło nam znaleźć i naprawić wąskie gardło wydajności, które byłoby trudniejsze do znalezienia, gdyby kod był WET.

Kiedy programiści i testerzy współpracują

Gdy testerzy i programiści zaczynają współpracować, dzieje się coś magicznego. Mniej czasu spędza się na przesyłaniu błędów tam iz powrotem przez system śledzenia defektów. Mniej czasu marnuje się, próbując dowiedzieć się, czy coś jest naprawdę błędem, czy nową funkcją, a więcej czasu poświęca się na tworzenie dobrego oprogramowania, aby spełnić oczekiwania klientów. Istnieje wiele możliwości rozpoczęcia współpracy jeszcze przed rozpoczęciem kodowania. Testerzy mogą pomóc klientom pisać i automatyzować testy akceptacyjne przy użyciu języka ich domeny za pomocą narzędzi takich jak Fit (Framework for Integrated Test). Kiedy te testy są przekazywane programistom przed rozpoczęciem kodowania, zespół ćwiczy programowanie oparte na testach akceptacyjnych (ATDD). Programiści piszą osprzęt do przeprowadzania testów, a następnie kodują, aby testy zaliczyły. Testy te stają się następnie częścią zestawu regresji. Gdy taka współpraca ma miejsce, testy funkcjonalne są kończone wcześnie, co daje czas na testy eksploracyjne w warunkach brzegowych lub poprzez przepływy pracy z szerszego obrazu. Możemy pójść o krok dalej. Jako tester mogę dostarczyć większość moich pomysłów na testowanie, zanim programiści zaczną kodować nową funkcję. Kiedy pytam programistów, czy mają jakieś sugestie, prawie zawsze dostarczają mi informacji, które pomagają mi w lepszym pokryciu testów lub pozwalają uniknąć spędzania dużej ilości czasu na niepotrzebnych testach. Często zapobiegaliśmy defektom ponieważ testy wyjaśniają wiele początkowych pomysłów. Na przykład w jednym projekcie, w którym byłem, testy dopasowania, które dałem programistom, wyświetlały oczekiwane wyniki zapytania, aby odpowiedzieć na wyszukiwanie z użyciem symboli wieloznacznych. Programista w pełni zamierzał kodować tylko pełne wyszukiwania słów. Udało nam się porozmawiać z klientem i ustalić prawidłową interpretację przed rozpoczęciem kodowania. Dzięki współpracy zapobiegliśmy usterce, co zaoszczędziło nam obojgu mnóstwo straconego czasu. Programiści mogą również współpracować z testerami, aby stworzyć udaną automatyzację. Rozumieją dobre praktyki kodowania i mogą pomóc testerom w stworzeniu solidnego pakietu automatyzacji testów, który działa dla całego zespołu. Często widziałem, jak projekty automatyzacji testów kończą się niepowodzeniem, ponieważ testy są źle zaprojektowane. Testy próbują testować zbyt wiele lub testerzy nie rozumieją wystarczająco technologii, aby móc utrzymać niezależne testy. Testerzy są często wąskim gardłem, dlatego warto, aby programiści współpracowali z nimi przy zadaniach takich jak automatyzacja. Współpraca z testerami w celu zrozumienia, co można przetestować wcześnie, być może poprzez dostarczenie prostego narzędzia, da programistom kolejny cykl informacji zwrotnych, który pomoże im w dostarczaniu lepszego kodu na dłuższą metę. Kiedy testerzy przestają myśleć, że ich jedynym zadaniem jest łamanie oprogramowania i znajdowanie błędów w kodzie programistów, programiści przestają myśleć, że testerzy "chcą je zdobyć" i są bardziej otwarci na współpracę. Kiedy programiści zaczynają zdawać sobie sprawę, że są odpowiedzialni za wbudowanie jakości w swój kod, testowalność kodu jest naturalnym produktem ubocznym, a zespół może wspólnie zautomatyzować więcej testów regresji. Zaczyna się magia udanej pracy zespołowej.

Napisz kod tak, jakbyś musiał go wspierać do końca życia

Możesz zapytać 97 osób, co każdy programista powinien wiedzieć i robić, a otrzymasz 97 różnych odpowiedzi. Może to być jednocześnie przytłaczające i onieśmielające. Wszystkie rady są dobre, wszystkie zasady solidne, a wszystkie historie fascynujące, ale od czego zacząć? Co ważniejsze, kiedy już zaczniesz, jak nadążasz za wszystkimi najlepszymi praktykami, których się nauczyłeś i jak uczynić je integralną częścią swojej praktyki programistycznej? Myślę, że odpowiedź tkwi w twoim nastroju lub, prościej, w twoim nastawieniu. Jeśli nie zależy Ci na innych programistach, testerach, menedżerach, osobach zajmujących się sprzedażą i marketingiem oraz użytkownikach końcowych, nie będziesz musiał na przykład stosować programowania opartego na testach lub pisać jasnych komentarzy w swoim kodzie. Myślę, że istnieje prosty sposób na dostosowanie swojego nastawienia i dążenie do dostarczania produktów najwyższej jakości:

Napisz kod tak, jakbyś miał go wspierać do końca życia.

Otóż to. Jeśli zaakceptujesz ten pogląd, wydarzy się wiele wspaniałych rzeczy. Gdybyś zaakceptował fakt, że któryś z twoich poprzednich lub obecnych pracodawców miał prawo zadzwonić do ciebie w środku nocy, prosząc o wyjaśnienie wyborów, których dokonałeś podczas pisania metody fooBar, stopniowo awansowałbyś w kierunku zostania doświadczonym programistą. Oczywiście chciałbyś wymyślić lepsze nazwy zmiennych i metod. Trzymałbyś się z dala od bloków kodu składających się z setek linii. Będziesz szukać, uczyć się i używać wzorców projektowych. Pisałbyś komentarze, testował swój kod i ciągle refaktorował. Wspieranie całego kodu, który kiedykolwiek napisałeś przez resztę swojego życia, powinno być również skalowalnym przedsięwzięciem. Nie miałbyś zatem innego wyboru, jak stać się lepszym, mądrzejszym i bardziej wydajnym. Jeśli się nad tym zastanowisz, kod, który napisałeś wiele lat temu, nadal wpływa na twoją karierę, czy ci się to podoba, czy nie. Pozostawiasz ślad swojej wiedzy, postawy, wytrwałości, profesjonalizmu, poziomu zaangażowania i stopnia zadowolenia z każdej metody, klasy i modułu, który projektujesz i piszesz. Ludzie będą formułować opinie o Tobie na podstawie kodu, który zobaczą. Jeśli te opinie są stale negatywne, zyskasz mniej ze swojej kariery, niż się spodziewałeś. Zadbaj o swoją karierę, swoich klientów i użytkowników z każdą linijką kodu do pisania kodu, tak jakbyś musiał wspierać go do końca życia.

Napisz małe funkcje, używając przykładów

Chcielibyśmy napisać kod, który jest poprawny i mieć pod ręką dowody na to, że jest poprawny. W obu kwestiach pomocne może być zastanowienie się nad "rozmiarem" funkcji. Nie w sensie ilości kodu, który implementuje funkcję - choć to jest interesujące - ale raczej rozmiaru funkcji matematycznej, którą manifestuje nasz kod. Na przykład w grze Go istnieje warunek zwany atari, w którym kamienie gracza mogą zostać przechwycone przez przeciwnika: kamień z dwoma lub więcej wolnymi polami sąsiadującymi z nim (tzw. liberties) nie znajduje się w atari. Obliczenie, ile swobód ma kamień, może być trudne, ale określenie atari jest łatwe, jeśli jest to znane. Możemy zacząć od napisania funkcji takiej jak ta:

boolean atari(int libertyCount)
libertyCount < 2

To jest większe niż się wydaje. Funkcję matematyczną można rozumieć jako zbiór, pewien podzbiór iloczynu kartezjańskiego zbiorów, które są jej dziedziną (tutaj int) i rozstępem (tutaj boolean). Gdyby te zestawy wartości miały taki sam rozmiar jak w Javie, byłoby 2L*(Integer.MAX_VALUE+(-1L*Integer.MIN_VALUE)+1L) lub 8 589 934 592 członków w zbiorze int×boolean. Połowa z nich to członkowie podzbioru, którym jest nasza funkcja, więc aby dostarczyć kompletny dowód, że nasza funkcja jest poprawna, musielibyśmy sprawdzić około 4,3 × 109 przykładów. To jest istota twierdzenia, że testy nie mogą udowodnić braku błędów. Testy mogą jednak wykazać obecność funkcji. Ale wciąż mamy problem z wielkością. Domena problematyczna nam pomaga. Natura Go oznacza, że liczba swobód kamienia nie jest żadną int, ale dokładnie jedną z {1,2,3,4}. Moglibyśmy więc alternatywnie napisać:

LibertyCount = {1,2,3,4}
boolean atari(LibertyCount libertyCount)
libertyCount == 1

Jest to o wiele bardziej wykonalne: obliczona funkcja jest teraz zbiorem składającym się z co najwyżej ośmiu elementów. W rzeczywistości cztery sprawdzone przykłady stanowiłyby dowód całkowitej pewności, że funkcja jest poprawna. To jeden z powodów, dla których do pisania programów dobrym pomysłem jest używanie typów blisko związanych z domeną problemu, a nie typów natywnych. Korzystanie z typów inspirowanych domenami często może znacznie zmniejszyć nasze funkcje. Jednym ze sposobów, aby dowiedzieć się, jakie powinny być te typy, jest znalezienie przykładów do sprawdzenia w terminach domeny problemowej, przed napisaniem funkcji.

Pisanie testów dla ludzi

Piszesz testy automatyczne dla części lub całości kodu produkcyjnego. Gratulacje! Piszesz swoje testy, zanim napiszesz kod? Nawet lepiej!! Samo robienie tego sprawia, że stajesz się jednym z pierwszych użytkowników w czołówce praktyki inżynierii oprogramowania. Ale czy piszesz dobre testy? Jak możesz powiedzieć? Jednym ze sposobów jest pytanie: "Dla kogo piszę testy?" Jeśli odpowiedź brzmi "Dla mnie, aby oszczędzić mi wysiłku naprawiania błędów" lub "Dla kompilatora, aby można je było wykonać", to istnieje prawdopodobieństwo, że nie piszesz najlepszych możliwych testów. Więc dla kogo powinieneś pisać testy? Dla osoby próbującej zrozumieć Twój kod. Dobre testy działają jako dokumentacja kodu, który testują. Opisują, jak działa kod. Dla każdego scenariusza użytkowania test(y):

•  Opisz kontekst, punkt początkowy lub warunki wstępne, które muszą być spełnione
•  Zilustruj sposób wywoływania oprogramowania
•  Opisz oczekiwane wyniki lub warunki końcowe, które należy zweryfikować. Różne scenariusze użycia będą miały nieco inne wersje każdego z nich.
Osoba próbująca zrozumieć twój kod powinna być w stanie przyjrzeć się kilku testom i porównując te trzy części testów, być w stanie zobaczyć, co powoduje, że oprogramowanie zachowuje się inaczej. Każdy test powinien wyraźnie ilustrować związek przyczynowo-skutkowy między tymi trzema częściami. Oznacza to, że to, co nie jest widoczne w teście, jest tak samo ważne jak to, co jest widoczne. Zbyt dużo kodu w teście rozprasza czytelnika nieistotnymi ciekawostkami. Jeśli to możliwe, ukryj takie ciekawostki za znaczącymi wywołaniami metod - refaktoryzacja metody ekstrakcji jest twoim najlepszym przyjacielem. I upewnij się, że nadałeś każdemu testowi znaczącą nazwę, która opisuje konkretny scenariusz użycia, aby osoba czytająca test nie musiała odtwarzać każdego testu, aby zrozumieć, jakie są różne scenariusze. Pomiędzy nimi nazwy klasy testowej i metody klasy powinny zawierać przynajmniej punkt początkowy i sposób wywoływania oprogramowania. Pozwala to na weryfikację pokrycia testowego poprzez szybkie skanowanie nazw metod. Przydatne może być również uwzględnienie oczekiwanych wyników w nazwach metod testowych, o ile nie powoduje to, że nazwy są zbyt długie, aby je zobaczyć lub przeczytać. Dobrym pomysłem jest również przetestowanie testów. Możesz sprawdzić, czy wykrywają błędy, które Twoim zdaniem wykrywają, wstawiając te błędy do kodu produkcyjnego (oczywiście swoją prywatną kopię, którą wyrzucisz). Upewnij się, że zgłaszają błędy w pomocny i znaczący sposób. Powinieneś również sprawdzić, czy twoje testy wyraźnie przemawiają do osoby próbującej zrozumieć twój kod. Jedynym sposobem, aby to zrobić, jest poproszenie kogoś, kto nie jest zaznajomiony z twoim kodem, aby przeczytał twoje testy i powiedział ci, czego się nauczyła. Słuchaj uważnie tego, co mówi. Jeśli czegoś wyraźnie nie rozumiała, to prawdopodobnie nie dlatego, że nie jest zbyt bystra. Bardziej prawdopodobne jest, że nie byłaś zbyt jasna. (Śmiało i odwróć role, czytając jej testy!)

Musisz dbać o kod

Nie trzeba Sherlocka Holmesa, aby zrozumieć, że dobrzy programiści piszą dobry kod. Źli programiści… nie. Tworzą potworności, które reszta z nas musi posprzątać. Chcesz napisać dobre rzeczy, prawda? Chcesz być dobrym programistą. Dobry kod nie wyskakuje z powietrza. To nie jest coś, co dzieje się przez przypadek, gdy planety się układają. Aby uzyskać dobry kod, musisz nad tym pracować. Ciężko. A dostaniesz dobry kod tylko wtedy, gdy naprawdę zależy ci na dobrym kodzie. Dobre programowanie nie rodzi się z samych kompetencji technicznych. Widziałem bardzo intelektualnych programistów, którzy potrafią tworzyć intensywne i imponujące algorytmy, którzy znają na pamięć standard swojego języka, ale piszą najstraszniejszy kod. Bolesne jest czytanie, bolesne w użyciu i bolesne w modyfikowaniu. Widziałem bardziej skromnych programistów, którzy trzymają się bardzo prostego kodu, ale piszą eleganckie i wyraziste programy, z którymi praca jest przyjemnością. Bazując na moim wieloletnim doświadczeniu w fabryce oprogramowania, doszedłem do wniosku, że prawdziwa różnica między odpowiednimi programistami a świetnymi programistami jest taka: postawa. Dobre programowanie polega na profesjonalnym podejściu i chęci napisania najlepszego oprogramowania, jakie możesz, w ramach rzeczywistych ograniczeń i presji fabryki oprogramowania. Kod do piekła jest wybrukowany dobrymi intencjami. Aby być doskonałym programistą, musisz wznieść się ponad dobre intencje, dbać o pozytywne nastawienie do kodowania i rozwijać zdrowe postawy. Świetny kod jest starannie tworzony przez mistrzów, a nie bezmyślnie wycinany przez niechlujnych programistów lub tworzony w tajemniczy sposób przez samozwańczych guru kodowania. Chcesz napisać dobry kod. Chcesz być dobrym programistą. Zależy Ci więc na kodzie:

•  W każdej sytuacji związanej z kodowaniem odmawiasz zhakowania czegoś, co tylko pozornie działa. Starasz się stworzyć elegancki kod, który jest wyraźnie poprawny (i ma dobre testy, które pokazują, że jest poprawny).
•  Piszesz kod, który jest wykrywalny (który inni programiści mogą łatwo zrozumieć i zrozumieć), który można utrzymać (który Ty lub inni programiści będziecie mogli łatwo modyfikować w przyszłości) i który jest poprawny (podejmujesz wszystkie kroki można stwierdzić, że rozwiązałeś problem, a nie tylko sprawiłeś, że wygląda na to, że program działa).
•  Dobrze współpracujesz z innymi programistami. Żaden programista nie jest wyspą. Niewielu programistów pracuje samotnie; większość pracuje w zespole programistów, albo w środowisku firmowym, albo nad projektem open source. Bierzesz pod uwagę innych programistów i tworzysz kod, który inni mogą przeczytać. Chcesz, aby zespół napisał najlepsze możliwe oprogramowanie, a nie abyś wyglądał na mądrego.
•  Za każdym razem, gdy dotykasz fragmentu kodu, starasz się pozostawić go lepiej niż go znalazłeś (albo lepiej ustrukturyzowany, lepiej przetestowany, bardziej zrozumiały).
o Dbasz o kod i programowanie, więc ciągle uczysz się nowych języków, idiomów i technik. Ale stosujesz je tylko wtedy, gdy jest to właściwe.

Na szczęście czytasz ten zbiór porad, ponieważ zależy Ci na kodzie. To cię interesuje. To Twoja pasja. Miłego programowania. Ciesz się cięciem kodu, aby rozwiązać trudne problemy. Twórz oprogramowanie, z którego będziesz dumny.

Twoi klienci nie mają na myśli tego, co mówią

Nigdy nie spotkałem jeszcze klienta, który nie był zbyt szczęśliwy, aby powiedzieć mi, czego chce - zwykle bardzo szczegółowo. Problem polega na tym, że klienci nie zawsze mówią Ci całą prawdę. Na ogół nie kłamią, ale mówią językiem klienta, a nie dewelopera. Używają ich terminów i kontekstów. Pomijają istotne szczegóły. Zakładają, że jesteś w ich firmie od 20 lat, tak jak oni. Sytuację pogarsza fakt, że wielu klientów w ogóle nie wie, czego chce! Niektórzy mogą rozumieć "duży obraz", ale rzadko są w stanie skutecznie przekazać szczegóły swojej wizji. Inni mogą być nieco lżejsi w pełnej wizji, ale wiedzą, czego nie chcą. Jak więc możesz dostarczyć projekt oprogramowania komuś, kto nie mówi ci całej prawdy o tym, czego chce? To dość proste. Po prostu więcej kontaktuj się z nimi. Rzuć wyzwanie swoim klientom wcześnie i rzucaj im wyzwania często. Nie po prostu przestawiaj co powiedzieli ci, czego chcieli w swoich słowach. Pamiętaj: nie mieli na myśli tego, co ci powiedzieli. Często wdrażam tę radę, wymieniając słowa klienta w rozmowie z nim i oceniając jego reakcję. Zdziwiłbyś się, ile razy termin klient ma zupełnie inne znaczenie niż termin klient. Jednak facet, który mówi, czego chce w swoim projekcie oprogramowania, będzie używał terminów zamiennie i oczekuje, że będziesz śledzić, o którym mówi. Będziesz zdezorientowany, a oprogramowanie, które napiszesz, ucierpi. Wielokrotnie omawiaj tematy ze swoimi klientami, zanim zdecydujesz, że rozumiesz, czego potrzebują. Spróbuj powtórzyć z nimi problem dwa lub trzy razy. Porozmawiaj z nimi o rzeczach, które dzieją się tuż przed lub tuż po temacie, o którym mówisz, aby uzyskać lepszy kontekst. Jeśli to w ogóle możliwe, miej kilka ludzi opowiadają Ci o tym samym temacie w osobnych rozmowach. Prawie zawsze będą opowiadać różne historie, które odkryją oddzielne, ale powiązane fakty. Dwie osoby mówiące ci na ten sam temat często będą się ze sobą sprzeczać. Największą szansą na sukces jest wymieszanie różnic przed rozpoczęciem bardzo złożonego tworzenia oprogramowania. Używaj pomocy wizualnych w swoich rozmowach. Może to być tak proste, jak użycie tablicy podczas spotkania, tak proste, jak stworzenie wizualnej makiety na wczesnym etapie projektowania, lub tak złożone, jak stworzenie funkcjonalnego prototypu. Powszechnie wiadomo, że korzystanie z pomocy wizualnych podczas rozmowy pomaga wydłużyć naszą koncentrację i zwiększa współczynnik retencji informacji. Wykorzystaj ten fakt i przygotuj swój projekt na sukces. W poprzednim życiu byłem "programistą multimedialnym" w zespole, który tworzył olśniewające projekty. Nasza klientka bardzo szczegółowo opisała swoje przemyślenia na temat wyglądu i stylu projektu. Ogólna kolorystyka omawiana na spotkaniach projektowych wskazywała na czarne tło prezentacji. Myśleliśmy, że mamy to przybite. Zespoły grafików zaczęły produkować setki warstwowych plików graficznych. Spędziliśmy mnóstwo czasu na formowaniu produktu końcowego. W dniu, w którym pokazaliśmy klientowi owoce naszej pracy, otrzymaliśmy zaskakujące wieści. Kiedy zobaczyła produkt, jej dokładne słowa dotyczące koloru tła brzmiały: "Kiedy powiedziałem czarny, miałem na myśli biały". Więc widzisz, nigdy nie jest tak jasne jak czarno-białe