PPy3/DobrePraktyki
Spis treści
Zasady ,,Dobrej praktyki"
Pod tym hasłem rozumiem zasady, które nie są regułami języka programowania, a program je naruszający może działać poprawnie a nawet całkiem nieźle. Nie są one też podatne na w pełni precyzyjne sformułowanie — a więc są to może bardziej wytyczne niż zasady. Nie należy ich też traktować jako obowiązujących w każdej sytuacji bez wyjątku; proponowałbym raczej przyjąć, że domyślnie się trzymamy tych zasad, a odstępujemy od nich raczej wyjątkowo i to wtedy, gdy takie odstępstwo ma wyraźne uzasadnienie.
Powodów dla przyjęcia takich zasad jest parę:
- pisząc program należy liczyć się z tym, że powinien on nie tylko poprawnie być wykonywany przez komputer (tj. interpreter), ale powinien również nadawać się do czytania przez ludzi — którzy mogą potrzebować go poprawić, rozszerzyć, przystosować do własnych celów, lub wręcz będą chcieli się czegoś z niego nauczyć;
- programowanie jest prawie zawsze częścią pracy zespołu, w którym uczestniczą zarówno osoby programujące, jak i wypełniające inne zadania. Dobrze jest przestrzegać zasad, które ułatwiają współpracę w ramach zespołu;
- nawet jeżeli tworzymy program samodzielnie i wyłącznie na własny (i może nam się wydawać, że jednorazowy) użytek, to nieraz się zdarzy że będziemy chcieli go użyć ponownie w przyszłości, i być może jakoś poprawić lub zmodyfikować. Można to uważać za szczególny przypadek pracy zespołowej ;) Ja, piszący pewien kod dzisiaj, i ja po dwóch miesiącach, to w zasadzie różne osoby, i łatwo dochodzi do sytuacji, w której ta druga kompletnie nie rozumie co ta pierwsza miała na myśli pisząc dany kod...
Zgodność ze specyfikacją
Piszemy program, który wykonuje to zadanie, które zostało postawione — a nie jakieś zbliżone, choćby nam się wydawało, że tak będzie lepiej. Jeżeli sformułowanie zadania zawiera konkretne nazwy funkcji, modułów itp., konkretne sygnatury — tzn. że funkcja przyjmuje takie a nie inne argumenty, i zwraca taką a nie inną wartość — to tego się trzymamy. Jeśli uważamy, że specyfikacja nie jest dobra i należałoby ją poprawić, to dyskutujemy to ze współpracownikami. Oczywiście nie dotyczy to kontekstu kolokwium czy sprawdzianu ;) Wówczas jeśli mamy jakiekolwiek wątpliwości co do postawionego zadania, to pytamy i to od razu o wyjaśnienie czy też ujednoznacznienie.
Szczególny przypadek: gdy program ma ustalać sposób swojego działania na podstawie opcji i/lub argumentów z linii poleceń, to nie zastępujemy tego zapytaniem interaktywnym (odwołaniem do input())! I vice versa. W 90% przypadków (liczba z sufitu) to pierwsze rozwiązanie jest właściwsze, ale najważniejsze jest to, na co się umówiliśmy z zespołem.
Sensowne nazwy
Niektórzy twierdzą, że nadawanie nazw obiektom występującym w kodzie programu jest najtrudniejszym zadaniem programisty. Być może to przesada, ale złe nazewnictwo może być dużym utrudnieniem w zrozumieniu kodu programu (przez człowieka, nie przez maszynę). Przez złe nazewnictwo mam na myśli nazwy nic nie znaczące, lub wręcz wprowadzające w błąd. To, jakie są zasady właściwego nazewnictwa jest i pewnie zawsze będzie sporne; natomiast łatwo poznać, kiedy nazewnictwo jest ewidentnie złe.
Pokrewnym zagadnieniem jest właściwe stosowanie tzw. stałych symbolicznych. Np. jeżeli w rachunkach potrzebna jest wartość liczby π, to nie piszemy w jej miejscu 1.14159... (czy ile cyfr znaczących jest nam potrzebnych), tylko korzystamy z math.pi lub numpy.pi. Ale dotyczy to nie tylko liczb o szczególnym znaczeniu; jeżeli pewien plik z jakichś powodów ma mieć szczególną nazwę (a nie — nazwę przekazywaną jako argument uruchomienia), lub nazwę domyślną, to wprowadzamy na samym początku kodu symbol oznaczający tę nazwę, i więcej jej w kodzie nie powtarzamy, tylko odwołujemy się do tego symbolu. I analogicznie w podobnych sytuacjach.
Właściwy podział zadań
Typowy program (taki malutki; jest wiele bardziej złożonych) ma do wykonania zazwyczaj trzy zadania:
- pozyskać dane wejściowe
- dokonać ich przetworzenia, lub wyliczyć na ich podstawie jakiś wynik
- przekazać ten wynik dalej, np. poinformować o nim użytkownika, lub zapisać do pliku.
Z wyjątkiem zupełnie trywialnych programów, mieszczących się w nie więcej niż kilku linijkach kodu, zadania te nie powinny być wykonywane łącznie w jednym bloku. Zadanie środkowe, jeżeli jest w nim cokolwiek nietrywialnego, powinno być wyodrębnione np. w funkcję (lub kilka), której zupełnie nie obchodzi skąd się biorą dane wejściowe (jej argumenty), ani co się stanie z wynikiem (wartością zwracaną). Jej zadaniem powinno być tylko i wyłącznie zrealizowanie algorytmu przetwarzania danych czy obliczenia wyniku. Sprzyja to czytelności kodu, ułatwia jego testowanie i ewentualne wielokrotne wykorzystanie. W szczególności, blok instrukcji zajmujący się pozyskaniem danych wejściowych i ,,zagospodarowaniem" wyniki powinien być opakowany w instrukcję warunkową if __name__ == '__main__':, gdyż instrukcje tam zawarte są niestosowne w przypadku, gdy chcielibyśmy funkcjonalność ,,algorytmiczną" kodu tego programu importować do wykorzystania w innym programie. Inaczej mówiąc, piszmy programy tak, aby dało się je wykorzystać bądź jako bezpośrednio wykonywalne, bądź jako moduły.
Ewidentnym gwałtem na tej zasadzie jest np.:
- gdy w kodzie funkcji przetwarzającej dane jest ,,zaszyta" nazwa pliku, z którego będą pobrane dane wejściowe
- gdy funkcja realizująca algorytm wypisuje jego wynik wywołując print(), zamiast go zwrócić instrukcją return
- gdy funkcja realizująca algorytm zawiera odwołania do input().
Właściwie w ,,algorytmicznej" części kodu nie powinno być w ogóle odwołań do input() ani do print(), ewentualnie te drugie mogą się pojawić na etapie testowania lub dla wyświetlania wskaźników postępu jeśli wykonanie programu może trwać długo.
CDN
poprzednie | strona główna | od początku ;)