Czyste programowanie podczas pisania kodu naukowego


169

Tak naprawdę nie piszę dużych projektów. Nie prowadzę ogromnej bazy danych ani nie obsługuję milionów wierszy kodu.

Mój kod to przede wszystkim rzeczy „skryptowe” - rzeczy do testowania funkcji matematycznych lub symulacji czegoś - „programowanie naukowe”. Najdłuższe programy, nad którymi pracowałem do tej pory, to kilkaset wierszy kodu, a większość programów, nad którymi pracuję, to około 150.

Mój kod to też bzdury. Zrozumiałem to innego dnia, gdy próbowałem znaleźć plik, który napisałem jakiś czas temu, ale że prawdopodobnie nadpisałem i że nie używam kontroli wersji, co prawdopodobnie powoduje, że wielu z was wzdryga się z bólu z powodu mojej głupoty.

Styl mojego kodu jest zawiły i jest wypełniony przestarzałymi komentarzami wskazującymi alternatywne sposoby zrobienia czegoś lub skopiowanymi wierszami kodu. Podczas gdy nazwy zmiennych zawsze zaczynają się bardzo ładnie i opisowo, gdy dodaję lub zmieniam rzeczy, np. Coś nowego, co ktoś chce przetestować, kod nakłada się na wierzch i zastępuje, a ponieważ czuję, że to powinno być przetestowane szybko, kiedy ja mam frameworkę, zaczynam używać kiepskich nazw zmiennych, a plik przechodzi do puli

W projekcie, nad którym teraz pracuję, jestem w fazie, w której wszystko to wraca, by mnie ugryźć na wielką skalę. Ale problem polega na tym (oprócz korzystania z kontroli wersji i tworzenia nowego pliku dla każdej nowej iteracji i rejestrowania go gdzieś w pliku tekstowym, co prawdopodobnie zdecydowanie pomoże sytuacji). Naprawdę nie wiem, jak postępować w celu poprawy mój rzeczywisty styl kodowania.

Czy testowanie jednostkowe jest konieczne do pisania mniejszych fragmentów kodu? Co powiesz na OOP? Jakie metody są dobre do szybkiego pisania dobrego, czystego kodu podczas „programowania naukowego”, a nie do pracy przy większych projektach?

Zadaję te pytania, ponieważ często samo programowanie nie jest zbyt skomplikowane. Chodzi bardziej o matematykę lub naukę, którą testuję lub badam przy programowaniu. Na przykład, czy klasa jest niezbędna, gdy dwie zmienne i funkcja prawdopodobnie mogłyby się tym zająć? (Należy wziąć pod uwagę, że ogólnie są to sytuacje, w których prędkość programu jest preferowana na szybszym końcu - jeśli wykonujesz 25 000 000 kroków czasowych symulacji, tak jakbyś chciał).

Być może jest to zbyt szerokie, a jeśli tak, przepraszam, ale patrząc na książki o programowaniu, często wydają się być adresowane do większych projektów. Mój kod nie potrzebuje OOP, i jest już dość cholernie krótki, więc nie jest taki jak „och, ale plik zostanie zmniejszony o tysiąc linii, jeśli to zrobimy!” Chcę wiedzieć, jak „zacząć od nowa” i programować czysto w tych mniejszych, szybszych projektach.

Z przyjemnością przedstawię bardziej szczegółowe informacje, ale im bardziej ogólna rada, tym bardziej przydatna, jak sądzę. Programuję w Pythonie 3.


Ktoś zasugerował duplikat. Wyjaśnię, że nie mówię o całkowitym ignorowaniu standardowych standardów programowania. Oczywiście istnieje powód, dla którego istnieją te standardy. Ale z drugiej strony, czy naprawdę ma sens pisanie kodu, który mówi OOP, gdy można by zrobić pewne standardowe rzeczy, byłby znacznie szybszy do napisania i byłby podobny poziom czytelności z powodu krótkiego program?

Są wyjątki. Ponadto prawdopodobnie istnieją standardy programowania naukowego wykraczające poza zwykłe standardy. Też pytam o to. Nie chodzi o to, czy normalne standardy kodowania powinny być ignorowane podczas pisania kodu naukowego, chodzi o pisanie czystego kodu naukowego!


Aktualizacja

Pomyślałem, że dodam aktualizację „nie całkiem tydzień później”. Wszystkie twoje porady były niezwykle pomocne. Teraz używam kontroli wersji - git, z git kraken dla interfejsu graficznego. Jest bardzo łatwy w użyciu i drastycznie wyczyścił moje pliki - nie trzeba już trzymać starych plików lub starych wersji kodu komentowanych „na wszelki wypadek”.

Zainstalowałem także pylint i uruchomiłem go na całym kodzie. Początkowo jeden plik uzyskał wynik ujemny; Nie jestem nawet pewien, jak to było możliwe. Mój główny plik zaczął się od ~ 1,83 / 10, a teraz ma ~ 9,1 / 10. Cały kod jest teraz dość dobrze zgodny ze standardami. Przebiegłem to także na własne oczy, aktualizując zmienne nazwy, które poszły ... hmm ... nie tak i szukałem sekcji do refaktoryzacji.

W szczególności zadałem ostatnie pytanie na tej stronie w sprawie refaktoryzacji jednej z moich głównych funkcji, a teraz jest o wiele czystsze i znacznie krótsze: zamiast długiej, rozdętej, wypełnionej / jeśli jeszcze wypełnionej funkcji, jest teraz mniejsza niż połowa rozmiar i znacznie łatwiej zrozumieć, co się dzieje.

Kolejnym krokiem jest wdrożenie „testów jednostkowych”. Rozumiem przez to plik, który mogę uruchomić na moim głównym pliku, który przegląda wszystkie zawarte w nim funkcje za pomocą instrukcji aser i try / wyjątki, co prawdopodobnie nie jest najlepszym sposobem na zrobienie tego i powoduje wiele duplikatów kodu, ale będę czytał dalej i spróbuję wymyślić, jak to zrobić lepiej.

Znacząco zaktualizowałem również dokumentację, którą już napisałem, i dodałem dodatkowe pliki, takie jak arkusz kalkulacyjny programu Excel, dokumentacja i powiązany dokument do repozytorium github. Teraz wygląda to trochę jak prawdziwy projekt programistyczny.

Więc ... Myślę, że to wszystko do powiedzenia: dziękuję .




8
Jeśli aktywnie chcesz poprawić swój kod, możesz rozważyć opublikowanie go w Code Review . Społeczność chętnie ci w tym pomoże.
hoffmale,

7
Kiedy mówisz „poza używaniem kontroli wersji i tworzeniem nowego pliku dla każdej nowej iteracji i zapisywaniem go w pliku tekstowym gdzieś” przez „i” masz na myśli „lub”, ponieważ jeśli używasz kontroli wersji, nie powinieneś nie będą kopiować wklejania wersji. Chodzi o to, że kontrola wersji zachowuje dla ciebie całą starą wersję
Richard Tingle

2
@mathreadler Nie sądzę, że rozumiesz. Tak, tylko ja prawdopodobnie każdy rzeczywiście czytam i popsuję kod (choć nigdy nie wiadomo, i mam kogoś, z kim pracuję, kto może programować, choć w innym języku) ... ale kod jest nadal bzdury. Będę musiał przeczytać to później i jeszcze raz dowiedzieć się, co do cholery robię. Jest to problem i mogę o tym świadczyć, ponieważ teraz odczuwam efekty, a rzeczy stały się łatwiejsze, ponieważ zaimplementowałem kontrolę wersji i inne sugerowane tutaj techniki.
wrzos

Odpowiedzi:


163

Jest to dość powszechny problem dla naukowców. Często to widziałem i zawsze wynika to z faktu, że programowanie jest czymś, co wybieracie jako narzędzie do wykonywania swojej pracy.

Twoje skrypty to bałagan. Idę wbrew zdrowemu rozsądkowi i powiem, że zakładając, że programujesz sam, nie jest tak źle! Nigdy więcej nie dotkniesz większości tego, co piszesz, więc spędzanie zbyt wiele czasu na pisaniu ładnego kodu zamiast tworzenia „wartości” (więc wynik skryptu) nie zrobi wiele dla ciebie.

Nadejdzie jednak czas, w którym musisz wrócić do czegoś, co zrobiłeś i zobaczyć dokładnie, jak coś działa. Ponadto, jeśli inni naukowcy będą musieli przejrzeć Twój kod, naprawdę ważne jest, aby był on tak jasny i zwięzły, jak to możliwe, aby każdy mógł go zrozumieć.

Twoim głównym problemem będzie czytelność, więc oto kilka wskazówek, jak poprawić:

Nazwy zmiennych:

Naukowcy uwielbiają używać zwięzłych notacji. Wszystkie równania matematyczne zwykle używają pojedynczych liter jako zmiennych i nie zdziwiłbym się, widząc wiele bardzo krótkich zmiennych w kodzie. To bardzo szkodzi czytelności. Kiedy wrócisz do swojego kodu, nie będziesz pamiętać, co reprezentują y, ja i x2, i spędzasz dużo czasu próbując to rozgryźć. Zamiast tego spróbuj jawnie nazwać zmienne, używając nazw, które reprezentują dokładnie to, czym są.

Podziel swój kod na funkcje:

Teraz, gdy zmieniłeś nazwę wszystkich swoich zmiennych, twoje równania wyglądają okropnie i mają wiele linii.

Zamiast pozostawiać je w głównym programie, przenieś to równanie do innej funkcji i odpowiednio je nazwij. Teraz zamiast dużej i pomieszanej linii kodu będziesz mieć krótkie instrukcje, które dokładnie powiedzą, co się dzieje i jakiego równania użyłeś. Poprawia to zarówno twój główny program, ponieważ nie musisz nawet patrzeć na faktyczne równanie, aby wiedzieć, co zrobiłeś, i sam kod równania, ponieważ w osobnej funkcji możesz nazwać swoje zmienne, jak chcesz, i wrócić do bardziej znane pojedyncze litery.

W tym myśleniu postaraj się znaleźć wszystkie fragmenty kodu, które coś reprezentują, zwłaszcza jeśli jest to coś, co musisz zrobić wiele razy w kodzie, i podziel je na funkcje. Dowiesz się, że Twój kod będzie szybko łatwiejszy do odczytania i że będziesz mógł korzystać z tych samych funkcji bez pisania więcej kodu.

Lukier na torcie, jeśli te funkcje są potrzebne w większej liczbie programów, możesz po prostu stworzyć dla nich bibliotekę, a będziesz mieć je zawsze dostępne.

Zmienne globalne:

Kiedy byłem początkujący, pomyślałem, że to świetny sposób na przekazywanie danych potrzebnych w wielu punktach mojego programu. Okazuje się, że istnieje wiele innych sposobów przekazywania informacji, a jedyne, co robią zmienne globalne, to przyprawianie ludzi o bóle głowy, ponieważ jeśli przejdziesz do przypadkowego punktu programu, nigdy nie dowiesz się, kiedy ta wartość była ostatnio używana lub edytowana, i śledzenie go będzie uciążliwe. Staraj się ich unikać, gdy tylko jest to możliwe.

Jeśli twoje funkcje muszą zwrócić lub zmodyfikować wiele wartości, utwórz klasę z tymi wartościami i przekaż je jako parametr, lub spraw, aby funkcja zwróciła wiele wartości (z nazwanymi krotkami) i przypisz te wartości do kodu wywołującego.

Kontrola wersji

Nie poprawia to bezpośrednio czytelności, ale pomaga wykonać wszystkie powyższe czynności. Ilekroć wprowadzasz jakieś zmiany, zobowiązuj się do kontroli wersji (lokalne repozytorium Git będzie wystarczające), a jeśli coś nie działa, spójrz na to, co zmieniłeś lub po prostu wycofaj! Ułatwi to refaktoryzację kodu i będzie siatką bezpieczeństwa, jeśli przypadkowo coś zepsujesz.

Mając to wszystko na uwadze, możesz napisać przejrzysty i bardziej efektywny kod, a także szybciej znaleźć możliwe błędy, ponieważ nie będziesz musiał przedzierać się przez gigantyczne funkcje i niechlujne zmienne.


56
Doskonała rada. Nie mogę jednak głosować za komentarzem „to nie jest takie złe”. To takie złe. Skrypty naukowe niskiej jakości stanowią duży problem dla powtarzalności analizy danych i częstym źródłem błędów w analizie. Pisanie dobrego kodu jest cenne nie tylko po to, abyś mógł go później zrozumieć, ale także, abyś mógł przede wszystkim uniknąć błędów.
Jack Aidley,

22
Jeśli kod jest implementacją dobrze znanej formuły, wówczas nazwy zmiennych zawierające jedną literę mogą być dobrym rozwiązaniem. Zależy od publiczności ... i bardziej od tego, czy czytelnik powinien wiedzieć, co oznaczają te nazwy.
cHao

11
@ cHao problem nie polega na tym, że zmienne są podłączone do formuły (stąd zalecenie „zmień nazwę w obrębie funkcji”), ale kiedy są odczytywane i modyfikowane poza nim, i kiedy zaczynają konfliktować z innymi zmiennymi w dół linii (na przykład widziałem ludzi potrzebujących trzech zmiennych „x”, które nazywają je x1, x2, x3)
BgrWorker

4
„Będę sprzeczny ze zdrowym rozsądkiem ...” Nie, nie jesteś. Przeciwstawiasz się panującemu dogmatowi, który sam w sobie jest sprzeczny ze zdrowym rozsądkiem. ;) To wszystko jest całkowicie rozsądna rada.
jpmc26,

3
Jako (były) programista naukowy ogólnie przyjmuję zasadę trzech. Jeśli skończę pisać trzy razy podobny kod, funkcjonalność zostanie rozwinięta i zapisana w osobnym module z dokumentacją (często tylko komentarze, ale to wystarczy). Ogranicza to chaos związany z programowaniem ad hoc i pozwala mi zbudować bibliotekę, którą mogę rozbudowywać w przyszłości.
kolekcjoner

141

Fizyk tutaj. Byłem tam

Argumentowałbym, że twój problem nie dotyczy wyboru narzędzi lub paradygmatów programowania (testowanie jednostkowe, OOP, cokolwiek innego). Chodzi o nastawienie , sposób myślenia. Fakt, że twoje zmienne nazwy są na początku dobrze wybrane, a ostatecznie są gówno, ujawnia dość. Jeśli myślisz o swoim kodzie jako „uruchom raz, a następnie wyrzuć”, to nieuchronnie będzie to bałagan. Jeśli myślisz o tym jako o dziele rzemiosła i miłości, będzie pięknie.

Uważam, że istnieje tylko jeden przepis na napisanie czystego kodu: napisz go dla człowieka, który go przeczyta, a nie dla tłumacza, który go uruchomi. Tłumacz nie dba o to, czy Twój kod jest bałaganem, ale obchodzi go czytelnik .

Jesteś naukowcem Prawdopodobnie możesz poświęcić dużo czasu na polerowanie artykułu naukowego. Jeśli twój pierwszy projekt wygląda na zawiły, przebudujesz go, dopóki logika nie popłynie w najbardziej naturalny sposób. Chcesz, aby koledzy go przeczytali i przekonali się, że argumenty są jasne. Chcesz, aby Twoi uczniowie mogli się z tego uczyć.

Pisanie czystego kodu jest dokładnie takie samo. Pomyśl o swoim kodzie jako szczegółowym objaśnieniu algorytmu, który tylko przypadkowo zdarza się do odczytu maszynowego. Wyobraź sobie, że zamierzasz opublikować go jako artykuł, który ludzie będą czytać. Zamierzasz nawet pokazać to na konferencji i poprowadzić publiczność po linii. Teraz przećwicz prezentację . Tak, linia po linii ! Żenujące, prawda? Więc posprzątaj swoje slajdy (err ... mam na myśli kod) i powtórz próbę. Powtarzaj, aż będziesz zadowolony z wyniku.

Byłoby jeszcze lepiej, jeśli po próbach możesz pokazać swój kod prawdziwym ludziom, a nie tylko wyobrażonym ludziom i swojemu przyszłemu. Przechodzenie przez nią linia po linii nazywa się „chodzeniem po kodzie” i nie jest to głupia praktyka.

Oczywiście wszystko to kosztuje. Napisanie czystego kodu zajmuje dużo więcej czasu niż napisanie niepotrzebnego kodu. Tylko Ty możesz ocenić, czy korzyści przewyższają koszty w konkretnym przypadku użycia.

Jeśli chodzi o narzędzia, powiedziałem wcześniej, że nie są one tak ważne. Gdybym jednak musiał wybrać jeden, powiedziałbym, że kontrola wersji jest najbardziej przydatna.


32
«Pisanie czystego kodu jest dokładnie takie samo [jak pisanie jasnego artykułu].» W pełni popieram to, dobrze powiedziane!
juandesant,

43
Jest to coś, o czym zapomina większość profesjonalnych programistów, ponieważ jest to takie oczywiste. Twój kod jest gotowy, gdy jest czytelny i modyfikowalny przez innego programistę. Nie, gdy działa i generuje prawidłowe dane wyjściowe. OP musi poświęcić dodatkową godzinę na refaktoryzację skryptu i komentowanie swojego kodu, aby był czytelny dla człowieka.
UEFI,

31
Chociaż pisanie czystego kodu zajmuje dużo więcej czasu niż pisanie kodu jednorazowego, o wiele ważniejsze jest to, że czytanie kodu jednorazowego zajmuje dużo więcej czasu niż czytanie czystego kodu.
user949300,

3
@UEFI Nie, to coś, czego nawet najbardziej profesjonalni programiści nie zdają sobie sprawy. Lub nie przejmuj się.
jpmc26,

2
Zgadzam się w 100%. Statistician został programistą, więc wykonuję sporo pracy naukowej. Czysty kod, z sensownymi komentarzami, ratuje życie, gdy musisz wrócić do tego kodu 1, 4 lub 12 miesięcy później. Czytanie kodu informuje, co robi kod. Czytanie komentarzy mówi ci, co powinien robić kod.
railsdog

82

Kontrola wersji prawdopodobnie zapewni Ci najwyższy zwrot z każdej zainwestowanej złotówki. Nie służy tylko do przechowywania długoterminowego, jest świetny do śledzenia krótkoterminowych eksperymentów i powrotu do ostatniej działającej wersji, przechowywania notatek po drodze.

Kolejne najbardziej przydatne są testy jednostkowe. Rzeczą w testach jednostkowych jest to, że nawet podstawy kodu z milionami wierszy kodu są testowane jednostkowo po jednej funkcji na raz. Testy jednostkowe wykonuje się na małym poziomie, na najniższym poziomie abstrakcji. Oznacza to, że zasadniczo nie ma różnicy między testami jednostkowymi napisanymi dla małych baz kodu a testami używanymi dla dużych. Jest ich po prostu więcej.

Testy jednostkowe to najlepszy sposób, aby powstrzymać się od zepsucia czegoś, co już działało, gdy naprawisz coś innego, lub przynajmniej powiedzieć ci szybko, kiedy to zrobisz. Są one w rzeczywistości bardziej przydatne, gdy nie jesteś tak wykwalifikowanym programistą, albo nie wiesz, jak lub nie chcesz pisać bardziej szczegółowego kodu, który jest tak skonstruowany, aby błędy były mniej prawdopodobne lub bardziej oczywiste.

Pomiędzy kontrolą wersji a pisaniem testów jednostkowych, kod stanie się znacznie bardziej przejrzysty. Po osiągnięciu płaskowyżu można nauczyć się innych technik czystszego kodowania.


78
Religijnie testuję jednostkowo większość mojego kodu, ale znalazłem testowanie eksploracyjne, kod naukowy jest mniej niż bezużyteczny . Metodologia zasadniczo nie działa tutaj. Nie znam żadnego naukowca zajmującego się obliczeniami w mojej dziedzinie, który testowałby swoje kody analityczne. Nie jestem pewien, jaki jest powód tego niedopasowania, ale jednym z powodów jest z pewnością to, że poza trywialnymi jednostkami ustalenie dobrych przypadków testowych jest trudne lub niemożliwe.
Konrad Rudolph

22
@KonradRudolph lewą w takich przypadkach może być czyste oddzielenie obaw między częściami twojego kodu, które mają jasno zdefiniowane zachowanie (przeczytaj to wejście, oblicz tę wartość) od części twojego kodu, które są naprawdę eksploracyjne lub przystosowują się do np. niektóre dane wyjściowe lub wizualizacje czytelne dla człowieka. Problemem może być tutaj to, że słaba separacja problemów prowadzi do zatarcia tych linii, co prowadzi do przekonania, że ​​testowanie jednostkowe w tym kontekście jest niemożliwe, co prowadzi do powrotu do początku w powtarzającym się cyklu.
Ant P

18
Na marginesie, kontrola wersji działa również całkiem nieźle w przypadku dokumentów LaTeX, ponieważ format nadaje się do różnicowania tekstu. W ten sposób możesz mieć repozytorium zarówno swoich dokumentów, jak i kodu, który je obsługuje. Sugeruję przyjrzenie się kontroli wersji rozproszonej, takiej jak Git. Jest trochę krzywej uczenia się, ale kiedy ją zrozumiesz, masz przyjemny czysty sposób na iterację w rozwoju i masz kilka ciekawych opcji korzystania z platformy takiej jak Github, która oferuje bezpłatne konta zespołowe dla naukowców .
Dan Bryant

12
@AntP Możliwe, że po prostu nie ma zbyt wiele kodu, który można znacznie przekształcić w dobrze zdefiniowane testowalne jednostki. Wiele kodu naukowego zasadniczo łączy ze sobą kilka bibliotek. Te biblioteki będą już dobrze przetestowane i uporządkowane, co oznacza, że ​​autor musi tylko pisać „klej”, a z mojego doświadczenia wynika, że ​​pisanie testów jednostkowych dla kleju, które nie są tautologiczne, jest prawie niemożliwe.
James_pic

7
„Pomiędzy kontrolą wersji a pisaniem testów jednostkowych w miarę upływu czasu kod stanie się o wiele czystszy”. To nie jest prawda. Mogę to poświadczyć osobiście. Żadne z tych narzędzi nie powstrzymuje Cię przed pisaniem kiepskiego kodu, a w szczególności pisanie kiepskich testów na wierzchu tego kiepskiego kodu sprawia, że ​​jeszcze trudniej jest go wyczyścić. Testy nie są magiczną srebrną kulą, a mówienie tak, jak są, jest okropną rzeczą dla każdego programisty, który wciąż się uczy (czyli wszystkich). Kontrola wersji zasadniczo nigdy nie powoduje uszkodzenia samego kodu, tak jak złe testy.
jpmc26,

29

(oprócz korzystania z kontroli wersji i tworzenia nowego pliku dla każdej nowej iteracji oraz zapisywania go gdzieś w pliku tekstowym, co prawdopodobnie znacznie poprawi sytuację)

Prawdopodobnie sam byś to wymyślił, ale jeśli musisz „ gdzieś to wszystko zapisać w pliku tekstowym ”, nie używasz systemu kontroli wersji w pełni. Użyj czegoś takiego jak Subversion, git lub Mercurial i napisz dobrą wiadomość zatwierdzenia przy każdym zatwierdzeniu, a będziesz miał dziennik, który służy plikowi tekstowemu, ale nie można go oddzielić od repozytorium.

Poza tym korzystanie z kontroli wersji jest najważniejszą rzeczą, którą możesz zrobić z powodu, o którym żadna z istniejących odpowiedzi nie wspomina: odtwarzalności wyników . Jeśli możesz użyć wiadomości z dziennika lub dodać notatkę do wyników z numerem rewizji, możesz być pewien, że będziesz w stanie zregenerować wyniki, a będziesz w lepszej pozycji, aby opublikować kod na papierze.

Czy testowanie jednostkowe jest konieczne do pisania mniejszych fragmentów kodu? Co powiesz na OOP? Jakie metody są dobre do szybkiego pisania dobrego, czystego kodu podczas „programowania naukowego”, a nie do pracy przy większych projektach?

Testowanie jednostek nigdy nie jest konieczne, ale jest użyteczne, jeśli (a) kod jest wystarczająco modułowy, aby można było testować jednostki, a nie całość; (b) możesz tworzyć testy. Idealnie byłoby, gdybyś mógł ręcznie napisać oczekiwany wynik, zamiast generować go za pomocą kodu, chociaż generowanie go za pomocą kodu może przynajmniej dać testy regresji, które pokażą, czy coś zmieniło jego zachowanie. Zastanów się, czy testy są bardziej podatne na błędy niż testowany kod.

OOP to narzędzie. Użyj go, jeśli to pomaga, ale nie jest to jedyny paradygmat. Zakładam, że tak naprawdę znasz programowanie proceduralne: jeśli tak jest, to w opisanym kontekście myślę, że przydałoby Ci się więcej studiując programowanie funkcjonalne niż OOP, aw szczególności dyscyplinę unikania skutków ubocznych, tam gdzie to możliwe. Python można pisać w bardzo funkcjonalnym stylu.


4
+1 dla komunikatów zatwierdzania; są jak komentarze, które nie mogą stać się nieaktualne, ponieważ są powiązane z wersją kodu, gdy faktycznie miały zastosowanie. Aby zrozumieć stary kod, łatwiej jest przejrzeć historię projektu (jeśli zmiany są wprowadzane z rozsądną dokładnością) niż przeczytać nieaktualne komentarze.
Silly Freak

Subversion, git i Mercurial nie są zamienne. Zdecydowanie zalecałbym używanie Git (lub Mercurial) z lokalnym repozytorium zamiast Subversion. W przypadku kodera solo wady Subversion nie stanowią większego problemu, ale nie jest to świetne narzędzie do wspólnego rozwoju, co może potencjalnie zdarzyć się w badaniach
mcottle,

2
@mcottle, ja osobiście wolę git, ale nie sądziłem, że to właściwe miejsce, aby zagłębić się w szczegóły na temat różnic, zwłaszcza że wybór jest jedną z aktywnych wojen religijnych. Lepiej zachęcić OP do korzystania z czegoś, niż odstraszyć je od tego obszaru, a decyzja w żadnym wypadku nie jest trwała.
Peter Taylor

21

Na studiach napisałem sam kod wymagający algorytmu. To trochę trudne orzechy do zgryzienia. Mówiąc grubo, wiele konwencji programistycznych opiera się na idei umieszczania informacji w bazie danych, pobierania ich we właściwym czasie, a następnie masowania tych danych w celu przedstawienia ich użytkownikowi, zwykle przy użyciu biblioteki do dowolnej matematyki lub matematyki. algorytmiczne części tego procesu. W przypadku tych programów wszystko, co słyszałeś o OOP, dzielenie kodu na krótkie funkcje i czynienie wszystkiego łatwo zrozumiałym na pierwszy rzut oka, tam gdzie to możliwe, jest doskonałą radą. Ale to nie do końca działa w przypadku kodu obciążonego algorytmem lub kodu, który implementuje złożone obliczenia matematyczne i niewiele więcej.

Jeśli piszesz skrypty do wykonywania obliczeń naukowych, prawdopodobnie masz w pracy zapisane równania lub algorytmy. Jeśli używasz nowych pomysłów, które odkryłeś sam, mam nadzieję, że opublikujesz je w swoich własnych artykułach. W takim przypadku reguła jest następująca: Chcesz, aby Twój kod czytał tak dużo, jak to możliwe, opublikowane równania. Oto odpowiedź na Software Engineering.SE z ponad 200 głosami popierającymi to podejście i wyjaśniającymi, jak to wygląda: Czy istnieje wymówka dla krótkich nazw zmiennych?

Innym przykładem jest kilka świetnych fragmentów kodu w Simbody , narzędziu do symulacji fizyki wykorzystywanym do badań i inżynierii fizyki. Te fragmenty mają komentarz pokazujący równanie używane do obliczeń, a następnie kod, który odczytuje jak najbliżej implementowanych równań, jak to możliwe.

ContactGeometry.cpp:

// t = (-b +/- sqrt(b^2-4ac)) / 2a
// Discriminant must be nonnegative for real surfaces
// but could be slightly negative due to numerical noise.
Real sqrtd = std::sqrt(std::max(B*B - 4*A*C, Real(0)));
Vec2 t = Vec2(sqrtd - B, -sqrtd - B) / (2*A);

ContactGeometry_Sphere.cpp:

// Solve the scalar Jacobi equation
//
//        j''(s) + K(s)*j(s) = 0 ,                                     (1)
//
// where K is the Gaussian curvature and (.)' := d(.)/ds denotes differentiation
// with respect to the arc length s. Then, j is the directional sensitivity and
// we obtain the corresponding variational vector field by multiplying b*j. For
// a sphere, K = R^(-2) and the solution of equation (1) becomes
//
//        j  = R * sin(1/R * s)                                        (2)
//          j' =     cos(1/R * s) ,                                      (3)
//
// where equation (2) is the standard solution of a non-damped oscillator. Its
// period is 2*pi*R and its amplitude is R.

// Forward directional sensitivity from P to Q
Vec2 jPQ(R*sin(k * s), cos(k * s));
geod.addDirectionalSensitivityPtoQ(jPQ);

// Backwards directional sensitivity from Q to P
Vec2 jQP(R*sin(k * (L-s)), cos(k * (L-s)));
geod.addDirectionalSensitivityQtoP(jQP);

9
Plus jeden do tworzenia własnego „kodu do odczytu tak podobnego do opublikowanych równań, jak to możliwe”. Przepraszamy, zwolennicy długich, znaczących nazw zmiennych. Najbardziej znaczące nazwy w kodeksie naukowym często są paskudne, krótkie i brutalne właśnie dlatego, że dokładnie taka konwencja została zastosowana w czasopiśmie naukowym, którą kod próbuje wdrożyć. W przypadku fragmentu kodu, który implementuje równania, które implementuje równania znalezione w artykule, często najlepiej jest trzymać się jak najbliżej nomenklatury w dokumencie, a jeśli jest to sprzeczne z dobrymi standardami kodowania, trudne.
David Hammen,

@DavidHammen: Jako student studiów szanuję to. Jako programista nalegałbym wtedy, aby na górze każdej funkcji znajdował się gigantyczny blok komentarzy opisujący zwykłym angielskim (lub wybranym językiem), co oznaczała każda zmienna, nawet jeśli tylko tymczasowy symbol zastępczy. W ten sposób mam przynajmniej odniesienie do przeszłości.
tonysdg,

1
@DavidHammen Poza tym obsługa Pythona dla UTF-8 w plikach źródłowych i proste reguły dla nazw zmiennych ułatwiają deklarowanie λlub φzamiast brzydkiego lambda_lub phy...
Mathias Ettinger

1
@tonysdg Masz już referencję; nazywa się to „Hammen i wsp. (2018)” (lub cokolwiek innego). Wyjaśni znaczenie zmiennych o wiele bardziej szczegółowo niż jakikolwiek blok komentarza. Powodem utrzymywania nazw zmiennych blisko notacji na papierze jest właśnie to, aby ułatwić połączenie tego, co jest w papierze, z tym, co jest w kodzie.
Nikt

17

Tak więc moją codzienną pracą jest publikacja i konserwacja danych badawczych dla systemu University of California. Kilku ludzi wspomniało o odtwarzalności i myślę, że to jest właśnie ten zasadniczy problem: udokumentowanie kodu w sposób, w jaki dokumentujesz wszystko, czego ktoś potrzebuje, aby odtworzyć eksperyment, a najlepiej napisanie kodu, który ułatwi to komuś innemu. odtworzyć eksperyment i sprawdzić wyniki pod kątem źródeł błędów.

Ale coś, o czym nie wspomniałem, co uważam za ważne, polega na tym, że agencje finansujące coraz częściej patrzą na publikację oprogramowania jako część publikacji danych oraz na uczynienie publikacji oprogramowania wymogiem otwartej nauki.

W tym celu, jeśli chcesz czegoś konkretnego, skierowanego raczej do badaczy niż do zwykłych programistów, nie mogę wystarczająco polecić organizacji Software Carpentry . Jeśli możesz wziąć udział w jednym z ich warsztatów , świetnie; jeśli masz tylko czas / dostęp do przeczytania niektórych artykułów na temat najlepszych praktyk w dziedzinie naukowego przetwarzania danych , to też dobrze. Z tego ostatniego:

Naukowcy zazwyczaj opracowują własne oprogramowanie do tych celów, ponieważ wymaga to znacznej wiedzy specyficznej dla danej dziedziny. W rezultacie ostatnie badania wykazały, że naukowcy zwykle poświęcają 30% lub więcej czasu na tworzenie oprogramowania. Jednak 90% lub więcej z nich jest samoukami i dlatego nie ma dostępu do podstawowych praktyk programistycznych, takich jak pisanie konserwowalnego kodu, korzystanie z kontroli wersji i śledzenia problemów, recenzji kodu, testowania jednostek i automatyzacji zadań.

Uważamy, że oprogramowanie jest tylko kolejnym rodzajem aparatu eksperymentalnego i powinno być budowane, sprawdzane i używane tak ostrożnie, jak każde urządzenie fizyczne. Chociaż większość naukowców dokłada starań, aby zweryfikować swoje wyposażenie laboratoryjne i terenowe, większość nie wie, jak niezawodne jest ich oprogramowanie. Może to prowadzić do poważnych błędów mających wpływ na główne wnioski z opublikowanych badań. …

Ponadto, ponieważ oprogramowanie jest często wykorzystywane do więcej niż jednego projektu i jest często ponownie wykorzystywane przez innych naukowców, błędy obliczeniowe mogą mieć nieproporcjonalny wpływ na proces naukowy. Ten rodzaj kaskadowego wpływu spowodował kilka wyraźnych wycofań, gdy błąd z kodu innej grupy został wykryty dopiero po publikacji.

Ogólny zarys zalecanych przez nich praktyk:

  1. Pisz programy dla ludzi, a nie komputerów
  2. Niech komputer wykona pracę
  3. Wprowadź zmiany przyrostowe
  4. Nie powtarzaj siebie (ani innych)
  5. Zaplanuj błędy
  6. Zoptymalizuj oprogramowanie tylko wtedy, gdy działa poprawnie
  7. Projekt dokumentu i cel, nie mechanika
  8. Współpracować

Artykuł szczegółowo opisuje każdy z tych punktów.


16

Czy naprawdę ma sens pisanie kodu, który mówi OOP, gdy można było zrobić pewne standardowe rzeczy, pisanie byłoby znacznie szybsze i byłby podobny poziom czytelności ze względu na krótki czas działania programu?

Osobista odpowiedź:
robię dużo skryptów również do celów naukowych. W przypadku mniejszych skryptów po prostu staram się przestrzegać ogólnych dobrych praktyk programistycznych (tj. Używając kontroli wersji, ćwicząc samokontrolę przy użyciu nazw zmiennych). Jeśli po prostu piszę coś, aby szybko otworzyć lub wizualizować zestaw danych, nie zawracam sobie głowy OOP.

Odpowiedź ogólna:
„To zależy”. Jeśli jednak zastanawiasz się, kiedy zastosować koncepcję programowania lub paradygmaty, oto kilka rzeczy do przemyślenia:

  • Skalowalność: czy skrypt będzie działał osobno, czy może zostanie użyty w większym programie? Jeśli tak, to czy większe programowanie korzysta z OOP? Czy kod w skrypcie można łatwo zintegrować z większym programem?
  • Modułowość: Zasadniczo kod powinien być modułowy. Jednak OOP dzieli kod na fragmenty w bardzo specjalny sposób. Czy tego rodzaju modułowość (tj. Dzielenie skryptu na klasy) ma sens w tym, co robisz?

Chcę wiedzieć, jak „zacząć od nowa” i programować czysto w tych mniejszych, szybszych projektach.

# 1: Zapoznaj się z tym, co tam jest:
Mimo że „tylko” piszesz skrypty (i naprawdę zależy ci tylko na komponencie naukowym), powinieneś poświęcić trochę czasu na poznanie różnych koncepcji i paradygmatów programowania. W ten sposób możesz mieć lepsze pojęcie o tym, czego powinieneś / nie powinieneś chcieć używać i kiedy. To może brzmieć nieco zniechęcająco. Być może nadal masz pytanie: „Od czego zacząć / od czego zacząć?” Staram się wyjaśnić dobry punkt wyjścia w następnych dwóch punktach.

# 2: Zacznij naprawiać to, co wiesz, że jest złe:
osobiście zacznę od rzeczy, o których wiem, że są złe. Zdobądź kontrolę nad wersją i zacznij dyscyplinować się, aby być lepszym dzięki tym nazwom zmiennych (jest to poważna walka). Naprawianie tego, co wiesz, że jest złe, może wydawać się oczywiste. Jednak z mojego doświadczenia wynika, że ​​naprawienie jednej rzeczy prowadzi mnie do czegoś innego i tak dalej. Zanim się zorientowałem, odsłoniłem 10 różnych rzeczy, które robiłem źle i wymyśliłem, jak je naprawić lub jak wdrożyć je w czysty sposób.

# 3: Znajdź partnera programistycznego:
jeśli „zaczynanie od nowa” nie wiąże się z podjęciem formalnych zajęć, rozważ nawiązanie współpracy z programistą i poproszenie go o sprawdzenie kodu. Nawet jeśli nie rozumieją naukowej części tego, co robisz, być może będą w stanie powiedzieć ci, co możesz zrobić, aby uczynić kod bardziej eleganckim.

# 4: Poszukaj konsorcjów:
Nie wiem, w jakim obszarze naukowym się znajdujesz. Ale w zależności od tego, co robisz w świecie naukowym, spróbuj poszukać konsorcjów, grup roboczych lub uczestników konferencji. Sprawdź, czy istnieją jakieś standardy, nad którymi pracują. Może to prowadzić do niektórych standardów kodowania. Na przykład wykonuję wiele prac geoprzestrzennych. Przeglądanie dokumentów konferencyjnych i grup roboczych zaprowadziło mnie do Open Geospatial Consortium . Jedną z rzeczy, które robią, jest praca nad standardami rozwoju geoprzestrzennego.

Mam nadzieję że to pomogło!


Uwaga dodatkowa: Wiem, że użyłeś OOP jako przykładu. Nie chciałem, żebyś myślał, że utknąłem tylko na tym, jak radzić sobie z pisaniem kodu za pomocą OOP. Po prostu łatwiej było napisać odpowiedź, kontynuując ten przykład.


Myślę, że # 3 jest najważniejszą kwestią - doświadczony programista może powiedzieć OP pojęć, których potrzebują (# 1), jak lepiej zorganizować skrypty i jak używać kontroli wersji (# 2).
Doc Brown

16

Polecam trzymać się zasady uniksowej: Keep It Simple, Stupid! (POCAŁUNEK)

Lub, mówiąc inaczej: Rób jedną rzecz na raz i rób to dobrze.

Co to znaczy? Przede wszystkim oznacza to, że twoje funkcje powinny być krótkie. Każda funkcja, która nie może być w pełni zrozumiana w celu, użyciu i implementacji w ciągu kilku sekund, jest zdecydowanie za długa. Prawdopodobnie robi kilka rzeczy naraz, z których każda powinna być funkcją własną. Więc podziel to.

Jeśli chodzi o linie kodu, moja heurystyka jest taka, że ​​10 linii to dobra funkcja, a wszystko powyżej 20 jest najprawdopodobniej badziewiem. Inni ludzie mają inną heurystykę. Ważną częścią jest ograniczenie długości do czegoś, co można właściwie uchwycić w jednej chwili.

Jak dzielisz długą funkcję? Najpierw poszukaj powtarzających się wzorców kodu. Następnie rozłóż te wzorce kodu, nadaj im opisową nazwę i obserwuj, jak kod się kurczy . Naprawdę najlepszym refaktoryzowaniem jest refaktoryzacja, która zmniejsza rozmiar kodu.

Jest to szczególnie prawdziwe, gdy dana funkcja została zaprogramowana za pomocą funkcji kopiuj-wklej. Ilekroć widzisz taki powtarzający się wzór, od razu wiesz, że prawdopodobnie powinien on zostać przekształcony we własną funkcję. Jest to zasada „ nie powtarzaj się” (DRY) . Za każdym razem, gdy naciskasz kopiuj-wklej, robisz coś złego! Zamiast tego utwórz funkcję.

Anegdota
Spędziłem kiedyś kilka miesięcy refaktoryzując kod, który miał funkcje około 500 linii każda. Po skończeniu cały kod był o tysiąc linii krótszy; Wytworzyłem wynik ujemny pod względem linii kodu. Byłem winien firmie ( http://www.geekherocomic.com/2008/10/09/programmers-salary-policy/index.html ). Mimo to mocno wierzę, że była to jedna z moich najcenniejszych prac, jakie kiedykolwiek wykonałem ...

Niektóre funkcje mogą być długie, ponieważ wykonują kilka różnych czynności jedna po drugiej. Nie są to naruszenia DRY, ale można je również podzielić. Rezultatem jest często funkcja wysokiego poziomu, która wywołuje garść funkcji, które realizują poszczególne kroki pierwotnych funkcji. Zasadniczo zwiększy to rozmiar kodu, ale dodane nazwy funkcji działają cuda, czyniąc kod bardziej czytelnym. Ponieważ teraz masz funkcję najwyższego poziomu, której wszystkie kroki są wyraźnie nazwane. Również po tym podziale jest jasne, który krok działa na których danych. (Argumenty funkcji. Nie używasz zmiennych globalnych, prawda?)

Dobrą heurystyką dla tego rodzaju podziału funkcji sekcyjnych jest zawsze, gdy masz ochotę napisać komentarz sekcji lub gdy znajdziesz kod sekcji w swoim kodzie. Jest to najprawdopodobniej jeden z punktów, w których należy podzielić funkcję. Komentarz do sekcji może również służyć do inspirowania nazwy nowej funkcji.

Zasady KISS i DRY mogą zabrać Cię daleko. Nie musisz zaczynać od OOP itp., Często możesz osiągnąć wielkie uproszczenia, po prostu stosując te dwa. Jednak na dłuższą metę opłaca się wiedzieć o OOP i innych paradygmatach, ponieważ zapewniają one dodatkowe narzędzia, których można użyć, aby poprawić kod programu.

Na koniec zapisz każdą akcję z zatwierdzeniem. Uwzględniasz coś w nowej funkcji, to jest zatwierdzenie . Łączysz dwie funkcje w jedną, ponieważ naprawdę robią to samo, to jest zatwierdzenie . Jeśli zmienisz nazwę zmiennej, jest to zatwierdzenie . Zobowiązuj się często. Jeśli mija dzień, a ty się nie popełniasz, prawdopodobnie zrobiłeś coś złego.


2
Świetne punkty na temat dzielenia długich metod. Kolejna dobra heurystyka dotycząca pierwszego akapitu po anegdocie: jeśli twoja metoda może być logicznie podzielona na sekcje i masz ochotę napisać komentarz wyjaśniający, co robi każda sekcja, to należy ją rozdzielić na komentarze. Dobra wiadomość, te komentarze prawdopodobnie dają ci dobry pomysł, jak nazwać nowe metody.
Jaquez

@Jaquez Ah, zupełnie o tym zapomniałem. Dzięki za przypomnienie. Zaktualizowałem swoją odpowiedź, aby uwzględnić to :-)
cmaster

1
Świetne punkty, chciałbym to uprościć, aby powiedzieć, że „OSUSZANIE” jest najważniejszym pojedynczym czynnikiem. Identyfikacja „powtórzeń” i ich usunięcie jest podstawą prawie wszystkich innych konstrukcji programistycznych. Innymi słowy, wszystkie konstrukcje programistyczne są, przynajmniej częściowo, pomocne w tworzeniu kodu DRY. Zacznij od powiedzenia „Nigdy nie powielania się”, a następnie poćwicz rozpoznawanie i eliminowanie go. Bądź bardzo otwarty na to, co może być duplikatem - nawet jeśli nie jest to podobny kod, może to być funkcja kopiowania ...
Bill K

11

Zgadzam się z innymi, że kontrola wersji rozwiąże wiele problemów od razu. Konkretnie:

  • Nie ma potrzeby utrzymywania listy wprowadzonych zmian lub posiadania dużej liczby kopii pliku itp., Ponieważ o to właśnie dba kontrola wersji.
  • Nigdy więcej utraconych plików z powodu nadpisywania itp. (O ile tylko trzymasz się podstaw; np. Unikaj „przepisywania historii”)
  • Nie ma potrzeby przechowywania przestarzałych komentarzy, martwego kodu itp. „Na wszelki wypadek”; kiedy już zaangażują się w kontrolę wersji, możesz je nuke'ować. To może wydawać się bardzo wyzwalające!

Powiedziałbym: nie przemyśl tego: po prostu użyj git. Trzymaj się prostych poleceń (np. Tylko jednej mastergałęzi), być może użyj GUI i powinieneś być w porządku. Jako bonus możesz użyć gitlab, github itp. Do darmowego publikowania i tworzenia kopii zapasowych;)

Powodem, dla którego napisałem tę odpowiedź, było poruszenie dwóch rzeczy, których możesz spróbować, o których nie wspomniałem powyżej. Pierwszym jest użycie twierdzeń jako lekkiej alternatywy dla testów jednostkowych. Testy jednostkowe zwykle siedzą „na zewnątrz” testowanej funkcji / modułu / czegokolwiek: zazwyczaj wysyłają pewne dane do funkcji, odbierają wynik z powrotem, a następnie sprawdzają niektóre właściwości tego wyniku. Jest to ogólnie dobry pomysł, ale może być niewygodny (szczególnie w przypadku kodu „wyrzucania”) z kilku powodów:

  • Testy jednostkowe muszą zdecydować, jakie dane przekażą funkcji. Dane muszą być realistyczne (w przeciwnym razie nie ma sensu ich testować), muszą mieć poprawny format itp.
  • Testy jednostkowe muszą mieć „dostęp” do rzeczy, które chcą potwierdzić. W szczególności testy jednostkowe nie mogą sprawdzić żadnych danych pośrednich wewnątrz funkcji; musielibyśmy rozdzielić tę funkcję na mniejsze części, przetestować te części i połączyć je gdzie indziej.
  • Zakłada się również, że testy jednostkowe są odpowiednie dla programu. Na przykład pakiety testowe mogą stać się „nieaktualne”, jeśli od ostatniego uruchomienia zaszły duże zmiany, a może nawet być kilka testów kodu, który nie jest już używany.

Asercje nie mają tych wad, ponieważ są sprawdzane podczas normalnego wykonywania programu. W szczególności:

  • Ponieważ są one uruchamiane w ramach normalnego wykonywania programu, mamy rzeczywiste dane do zabawy. Nie wymaga to osobnej kuracji i (z definicji) jest realistyczny i ma prawidłowy format.
  • Asercje można zapisać w dowolnym miejscu kodu, dzięki czemu możemy je umieścić w dowolnym miejscu, w którym mamy dostęp do danych, które chcemy sprawdzić. Jeśli chcemy przetestować jakąś wartość pośrednią w funkcji, możemy po prostu umieścić pewne twierdzenia na środku tej funkcji!
  • Ponieważ są napisane w linii, asercje nie mogą „zsynchronizować się” ze strukturą kodu. Jeśli upewnimy się, że asercje są sprawdzane domyślnie, nie musimy się też martwić, że staną się „nieaktualne”, ponieważ od razu przekonamy się, czy przejdą one następnym razem, gdy uruchomimy program!

Jako czynnik podaje się szybkość, w którym to przypadku sprawdzanie asercji może być niepożądane w tej pętli (ale nadal przydatne do sprawdzania konfiguracji i późniejszego przetwarzania). Jednak prawie wszystkie implementacje asercji umożliwiają ich wyłączenie; na przykład w Pythonie można je najwyraźniej wyłączyć, uruchamiając z tą -Oopcją (nie wiedziałem o tym, ponieważ nigdy wcześniej nie czułem potrzeby wyłączania moich twierdzeń). Polecam, aby pozostawić je nadomyślnie; jeśli cykl kodowania / debugowania / testowania zwalnia, być może lepiej będzie przetestować mniejszy podzbiór danych lub wykonać mniejszą liczbę iteracji podczas symulacji lub cokolwiek innego. Jeśli skończysz z wyłączaniem asercji w uruchomieniach nietestowych ze względu na wydajność, pierwszą rzeczą, którą zalecam, jest sprawdzenie, czy faktycznie są one źródłem spowolnienia! (Bardzo łatwo jest się łudzić, jeśli chodzi o wąskie gardła w wydajności)

Moja ostatnia rada to skorzystanie z systemu kompilacji, który zarządza twoimi zależnościami. Osobiście używam do tego Nixa , ale słyszałem też dobre rzeczy o Guixie . Istnieją również alternatywy, takie jak Docker, które są znacznie mniej przydatne z naukowego punktu widzenia, ale być może są nieco bardziej znane.

Systemy takie jak Nix dopiero ostatnio stają się (trochę) popularne, a niektórzy mogą uważać je za przesadne z powodu „wyrzucania” kodu, jak to opisujesz, ale ich korzyść z odtwarzalności obliczeń naukowych jest ogromna. Rozważ skrypt powłoki do przeprowadzenia eksperymentu, taki jak ten (np. run.sh):

#!/usr/bin/env bash
set -e
make all
./analyse < ./dataset > output.csv

Zamiast tego możemy przepisać go na „pochodną” Nix, jak to (np. run.nix):

with import <nixpkgs> {};
runCommand "output.csv" {} ''
  cp -a ${./.} src
  cd src
  make all
  ./analyse < ./dataset > $out
''

Między nimi ''...''znajduje się kod bash, taki sam, jaki mieliśmy wcześniej, z tym wyjątkiem, że ${...}można go użyć do „łączenia” treści innych ciągów (w tym przypadku ./., które rozwiną się do ścieżki zawierającej katalog run.nix). with import ...Linia importuje Nix w standardowej biblioteki , która zapewnia runCommanddo uruchamiania kodu bash. Możemy uruchomić nasz eksperyment przy użyciu nix-build run.nix, który wyznaczy ścieżkę podobną do /nix/store/1wv437qdjg6j171gjanj5fvg5kxc828p-output.csv.

Co nas to kupuje? Nix automatycznie skonfiguruje „czyste” środowisko, które ma dostęp tylko do rzeczy, o które wyraźnie prosiliśmy. W szczególności nie ma dostępu do zmiennych takich jak $HOMEżadne oprogramowanie systemowe, które zainstalowaliśmy. To uniezależnia wynik od szczegółów naszego aktualnego komputera, takich jak zawartość ~/.configlub wersje programów, które zainstalowaliśmy; AKA rzeczy, które uniemożliwiają innym osobom powielanie naszych wyników! Właśnie dlatego to dodałemcppolecenie, ponieważ projekt nie będzie domyślnie dostępny. Może to wydawać się denerwujące, że oprogramowanie systemowe nie jest dostępne dla skryptu Nix, ale działa też w drugą stronę: nie potrzebujemy niczego zainstalowanego w naszym systemie (poza Nix), aby korzystać z niego w skrypcie; po prostu o to poprosimy, a Nix uruchomi się i pobierze / skompiluje / cokolwiek będzie konieczne (większość rzeczy zostanie pobrana jako pliki binarne; standardowa biblioteka jest również ogromna!). Na przykład, jeśli chcemy kilka konkretnych pakietów Python i Haskell, dla niektórych konkretnych wersji tych języków, a także inne śmieci (bo dlaczego nie?):

with import <nixpkgs> {};
runCommand "output.csv"
  {
    buildInputs = [
      gcc49 libjson zlib
      haskell.packages.ghc802.pandoc
      (python34.withPackages (pyPkgs: [
        pyPkgs.beautifulsoup4 pyPkgs.numpy pyPkgs.scipy
        pyPkgs.tensorflowWithoutCuda
      ]))
    ];
  }
  ''
    cp -a ${./.} src
    cd src
    make all
    ./analyse < ./dataset > $out
  ''

To samo nix-build run.nixuruchomi to, pobierając wszystko, o co poprosiliśmy w pierwszej kolejności (i buforuj wszystko, na wypadek, gdybyśmy chcieli później). Dane wyjściowe (dowolny wywołany plik / katalog $out) będą przechowywane przez Nix, czyli ścieżkę, którą wypluwa. Jest identyfikowany przez kryptograficzny skrót wszystkich danych wejściowych, o które poprosiliśmy (zawartość skryptu, inne pakiety, nazwy, flagi kompilatora itp.); te inne pakiety są identyfikowane skrótami ich danych wejściowych i tak dalej, że mamy pełny łańcuch proweniencji dla wszystkiego, od razu do wersji GCC, która skompilowała wersję GCC, która skompilowała bash, i tak dalej!

Mam nadzieję, że pokazałem, że to dużo nas kupuje za kod naukowy i jest dość łatwo zacząć. Zaczynają też być traktowane bardzo poważnie przez naukowców, np. (Najlepszy hit w Google) https://dl.acm.org/citation.cfm?id=2830172, więc może być cenną umiejętnością do kultywowania (tak jak programowanie)


2
Bardzo szczegółowa przydatna odpowiedź - Naprawdę lubię inne odpowiedzi, ale stwierdzenia brzmią jak bardzo przydatny pierwszy krok.
wrzos

9

Bez wchodzenia w pełnoprawną kontrolę wersji + pakowanie + testy jednostkowe, które są dobrymi praktykami programistycznymi, które powinieneś spróbować w pewnym momencie osiągnąć, jednym z pośrednich rozwiązań, które moim zdaniem pasowałoby, jest użycie Jupiter Notebook . Wydaje się, że lepiej to integruje z obliczeniami naukowymi.

Ma tę zaletę, że możesz mieszać swoje myśli z kodem; wyjaśnianie, dlaczego podejście jest lepsze niż inne, i pozostawianie starego kodu w niezmienionej postaci w sekcji ad-hoc. Poza tym prawidłowe użycie komórek w naturalny sposób prowadzi do fragmentacji kodu i uporządkowania go w funkcje, które mogą pomóc w jego zrozumieniu.


1
Dodatkowo pomaga to w odtwarzalności - możesz uruchomić dokładnie ten sam kod, aby na przykład wygenerować publikację lub wrócić do czegoś, co odłożyłeś kilka miesięcy temu, na przykład w celu włączenia komentarzy recenzenta.
afaulconbridge

Dla kogoś, kto chce przeczytać więcej, nazywa się to także programowaniem czytania i pisania.
llrs

6

Najlepsze odpowiedzi są już dobre, ale chciałem odpowiedzieć bezpośrednio na niektóre pytania.

Czy testowanie jednostkowe jest konieczne do pisania mniejszych fragmentów kodu?

Rozmiar kodu nie jest bezpośrednio związany z potrzebą testów jednostkowych. Jest to powiązane pośrednio: testy jednostkowe są bardziej wartościowe w złożonych bazach kodów, a małe bazy kodów nie są na ogół tak złożone jak większe.

Testy jednostkowe sprawdzają się w przypadku kodu, w którym łatwo popełnić błąd lub gdy będzie wiele implementacji tego kodu. Testy jednostkowe niewiele pomagają w bieżącym rozwoju, ale wiele robią, aby zapobiec popełnianiu błędów w przyszłości, które powodują, że istniejący kod nagle zachowuje się nieprawidłowo (nawet jeśli tego nie dotknąłeś).

Załóżmy, że masz aplikację, w której biblioteka A wykonuje wyrównywanie liczb, a biblioteka B stosuje twierdzenie Pitagorasa. Oczywiście, B zależy od A. Musisz naprawić coś w bibliotece A, i powiedzmy, że wprowadzasz błąd, który dzieli liczby na kwadraty, zamiast ich kwadratu.

Biblioteka B nagle zacznie źle się zachowywać, być może wprowadzając wyjątki lub po prostu dając nieprawidłowe wyniki. A kiedy spojrzysz na historię wersji biblioteki B, zobaczysz, że jest nietknięta. Problematycznym rezultatem końcowym jest to, że nie masz żadnych wskazówek co do tego, co może pójść źle, i będziesz musiał debugować zachowanie B, zanim zdasz sobie sprawę, że problem jest w A. To zmarnowany wysiłek.

Wprowadź testy jednostkowe. Testy te potwierdzają, że biblioteka A działa zgodnie z przeznaczeniem. Jeśli wprowadzisz błąd w bibliotece A, który powoduje, że zwraca on złe wyniki, wówczas testy jednostkowe go wychwycą. Dlatego nie utkniesz przy próbie debugowania biblioteki B.
Jest to poza twoim zasięgiem, ale w ciągłym rozwoju integracji testy jednostkowe są wykonywane za każdym razem, gdy ktoś popełni jakiś kod, co oznacza, że ​​będziesz wiedział, że coś złamałeś jak najszybciej.

Szczególnie w przypadku skomplikowanych operacji matematycznych testy jednostkowe mogą być błogosławieństwem. Wykonujesz kilka przykładowych obliczeń, a następnie piszesz testy jednostkowe, które porównują obliczoną moc wyjściową i rzeczywistą moc wyjściową (w oparciu o te same parametry wejściowe).

Pamiętaj jednak, że testy jednostkowe nie pomogą ci stworzyć dobrego kodu, ale raczej go utrzymają . Jeśli zwykle piszesz kod raz i nigdy go nie odwiedzasz, testy jednostkowe będą mniej korzystne.

Co powiesz na OOP?

OOP to sposób myślenia o różnych podmiotach, na przykład:

Kiedy Customerchce kupić Product, rozmawia z, Vendoraby otrzymać Order. AccountantWtedy zapłacić Vendor.

Porównaj to z tym, jak funkcjonalny programista myśli o rzeczach:

Kiedy klient tego chce purchaseProduct(), on mu talktoVendor()to zrobi sendOrder(). Księgowy wtedy payVendor().

Jabłka i Pomarańcze. Żadna z nich nie jest obiektywnie lepsza od drugiej. Jedną interesującą rzeczą do odnotowania jest to, że w przypadku OOP Vendorjest wspomniana dwa razy, ale odnosi się do tej samej rzeczy. Jednak dla programowania funkcyjnego, talktoVendor()a payVendor()to dwie odrębne rzeczy.
To pokazuje różnicę między podejściami. Jeśli między tymi dwoma akcjami występuje dużo wspólnej logiki specyficznej dla dostawcy, wówczas OOP pomaga zmniejszyć duplikację kodu. Jeśli jednak nie ma wspólnej logiki między nimi, połączenie ich w jedną Vendorjest daremną pracą (a zatem programowanie funkcyjne jest bardziej wydajne).

Najczęściej obliczenia matematyczne i naukowe są odrębnymi operacjami, które nie opierają się na niejawnej wspólnej logice / formułach. Z tego powodu programowanie funkcjonalne jest częściej używane niż OOP.

Jakie metody są dobre do szybkiego pisania dobrego, czystego kodu podczas „programowania naukowego”, a nie do pracy przy większych projektach?

Twoje pytanie sugeruje, że definicja „dobrego, czystego kodu” zmienia się niezależnie od tego, czy zajmujesz się programowaniem naukowym, czy pracujesz nad większymi projektami (zakładam, że masz na myśli przedsiębiorstwo).

Definicja dobrego kodu nie zmienia się. Konieczność uniknięcia złożoności (które mogą być wykonywane przez pisanie czystego kodu), jednak nie zmienia.

Ten sam argument powraca tutaj.

  • Jeśli nigdy nie odwiedzasz starego kodu i nie rozumiesz w pełni logiki bez konieczności dzielenia go na przedziały, nie wkładaj nadmiernego wysiłku, aby wszystko było łatwe do utrzymania.
  • Jeśli ponownie odwiedzasz stary kod lub wymagana logika jest zbyt skomplikowana, abyś mógł zająć się nią od razu (co wymaga podzielenia rozwiązań na części), to skup się na pisaniu czystego zamykania wielokrotnego użytku.

Zadaję te pytania, ponieważ często samo programowanie nie jest zbyt skomplikowane. Chodzi bardziej o matematykę lub naukę, którą testuję lub badam przy programowaniu.

Rozróżnia mnie tutaj, ale kiedy patrzysz wstecz na istniejący kod, patrzysz zarówno na matematykę, jak i programowanie. Jeśli któryś jest wymyślony lub złożony, będziesz miał trudności z jego odczytaniem.

Na przykład, czy klasa jest niezbędna, gdy dwie zmienne i funkcja prawdopodobnie mogłyby się tym zająć?

Pomijając zasady OOP, głównym powodem, dla którego piszę klasy do przechowywania kilku wartości danych, jest to, że upraszcza deklarowanie parametrów metody i zwracanie wartości. Na przykład, jeśli mam wiele metod, które używają lokalizacji (para lat / lon), to szybko zmęczy mnie konieczność pisania float latitude, float longitudei wolę pisać Location loc.

Jest to jeszcze bardziej skomplikowane, gdy weźmie się pod uwagę, że metody generalnie zwracają jedną wartość (chyba że istnieją funkcje specyficzne dla języka w celu zwrócenia większej liczby wartości), a rzeczy takie jak lokalizacja chciałyby zwrócić dwie wartości (lat + lon). To zachęca do stworzenia Locationklasy upraszczającej kod.

Na przykład, czy klasa jest niezbędna, gdy dwie zmienne i funkcja prawdopodobnie mogłyby się tym zająć?

Inną ciekawą rzeczą do odnotowania jest to, że możesz używać OOP bez mieszania wartości danych i metod. Nie każdy programista się tutaj zgadza (niektórzy nazywają to antypatternem), ale możesz mieć anemiczne modele danych, w których masz oddzielne klasy danych (pola wartości w sklepie) i klasy logiki (metody w sklepie).
To oczywiście widmo. Nie musisz być całkowicie anemiczny, możesz go użyć, jeśli uznasz to za stosowne.

Na przykład metoda, która po prostu łączy imię i nazwisko osoby, nadal może być umieszczona w Personsamej klasie, ponieważ nie jest to tak naprawdę „logika”, ale raczej obliczona wartość.

(Należy wziąć pod uwagę, że ogólnie są to sytuacje, w których prędkość programu jest preferowana na szybszym końcu - jeśli wykonujesz 25 000 000 kroków czasowych symulacji, tak jakbyś chciał).

Klasa jest zawsze tak duża, jak suma jej pól. Biorąc przykład Locationponownie, który składa się z dwóch floatwartości, należy zauważyć, że pojedynczy Locationobiekt zajmie tyle pamięci, co dwie osobne floatwartości.

W tym sensie nie ma znaczenia, czy używasz OOP, czy nie. Ślad pamięci jest taki sam.

Sama wydajność również nie jest dużą przeszkodą do pokonania. Różnica między np. Używaniem metody globalnej lub metody klasowej nie ma nic wspólnego z wydajnością środowiska wykonawczego, ale ma wszystko wspólnego z generowaniem kodu bajtowego w czasie kompilacji.

Pomyśl o tym w ten sposób: to, czy napiszę przepis na ciasto w języku angielskim czy hiszpańskim, nie zmienia faktu, że ciasto zajmie 30 minut (= wydajność w czasie wykonywania). Jedyne, co zmienia język przepisu, to sposób, w jaki kucharz miesza składniki (= kompiluje kod bajtowy).

W przypadku Pythona nie trzeba jawnie kompilować kodu przed jego wywołaniem. Jeśli jednak nie wykonasz wstępnej kompilacji, kompilacja nastąpi podczas próby wykonania kodu. Kiedy mówię „środowisko uruchomieniowe”, mam na myśli samą realizację, a nie kompilację, która może poprzedzać wykonanie.


6

Korzyści z czystego kodu naukowego

  • ... patrząc na książki o programowaniu, często wydają się być adresowane do większych projektów.

  • ... czy naprawdę ma sens pisanie kodu, który mówi OOP, gdy można było zrobić jakieś standardowe rzeczy, byłby o wiele szybszy do napisania i byłby podobny poziom czytelności z powodu krótkości programu?

Pomocne może być rozważenie kodu z perspektywy przyszłego programisty.

  • Dlaczego otworzyli ten plik?
  • Czego oni szukają?

Z mojego doświadczenia,

Czysty kod powinien ułatwić weryfikację wyników

  • Ułatw użytkownikom sprawdzenie dokładnie, co muszą zrobić, aby uruchomić program.
  • Możesz podzielić swój program, aby poszczególne algorytmy mogły być testowane osobno.

  • Unikaj pisania funkcji z sprzecznymi z intuicją skutkami ubocznymi, gdy jedna niepowiązana operacja powoduje, że inna operacja zachowuje się inaczej. Jeśli nie możesz tego uniknąć, udokumentuj, czego potrzebuje Twój kod i jak go skonfigurować.

Czysty kod może służyć jako przykładowy kod dla przyszłych programistów

Jasne komentarze (w tym te, które pokazują, jak należy wywoływać funkcje) i dobrze rozdzielone funkcje mogą mieć ogromną różnicę w tym, ile czasu zajmuje dopiero rozpoczynanie pracy (lub przyszłość użytkownika), aby zrobić coś pożytecznego z pracy.

Oprócz tego stworzenie prawdziwego „API” dla algorytmu może sprawić, że będziesz lepiej przygotowany, jeśli zdecydujesz się przekształcić skrypty w prawdziwą bibliotekę, z której będzie mógł korzystać ktoś inny.

Rekomendacje

Wzory matematyczne „Cytuj” za pomocą komentarzy.

  • Dodawaj komentarze do formuł matematycznych „cytuj”, zwłaszcza jeśli korzystałeś z optymalizacji (tożsamości trigg, seria Taylora itp.).
  • Jeśli otrzymałeś formułę z książki, dodaj komentarz z napisem John Smith Method from Some Book 1st Ed. Section 1.2.3 Pg 180: jeśli znalazłeś formułę na stronie internetowej lub w gazecie, również to przytocz.
  • Zalecam unikanie komentarzy typu „tylko link”, upewnij się, że odwołujesz się do metody z nazwy, aby umożliwić użytkownikom przeglądanie w Google, natrafiłem na niektóre komentarze „tylko link”, które przekierowywały na stare wewnętrzne strony i mogą być bardzo frustrujące .
  • Możesz spróbować wpisać formułę w swoim komentarzu, jeśli nadal jest łatwy do odczytania w Unicode / ASCII, ale może to być bardzo niewygodne (komentarze do kodu nie są LaTeX).

Mądrze używaj komentarzy

Jeśli możesz poprawić czytelność swojego kodu, używając dobrych nazw zmiennych / nazw funkcji, zrób to najpierw. Pamiętaj, że komentarze pozostaną na zawsze, dopóki ich nie usuniesz, więc staraj się robić komentarze, które nie będą nieaktualne.

Użyj opisowych nazw zmiennych

  • Zmienne jednoliterowe mogą być najlepszą opcją, jeśli są częścią formuły.
  • Przyszli czytelnicy mogą mieć kluczowe znaczenie dla spojrzenia na napisany kod i porównania go z implementowanym równaniem.
  • W razie potrzeby rozważ dodanie przyrostka, aby opisać jego prawdziwe znaczenie, np. xBar_AverageVelocity
  • Jak wspomniano wcześniej, zalecam wyraźne wskazanie formuły / metody, której używasz z nazwy w komentarzu.

Napisz kod, aby uruchomić program ze znanymi dobrymi i znanymi złymi danymi.

Czy testowanie jednostkowe jest konieczne do pisania mniejszych fragmentów kodu?

Myślę, że testowanie jednostkowe może być pomocne. Myślę, że najlepszą formą testowania jednostkowego kodu naukowego jest seria testów, które działają na znanych złych i dobrych danych.

Napisz kod, aby uruchomić algorytm i sprawdź, jak daleko wynik odbiega od oczekiwanych. Pomoże to znaleźć (potencjalnie bardzo złe i trudne do znalezienia) problemy, w których przypadkowo utrwalisz kod powodujący fałszywie dodatni wynik lub popełnisz błąd, który powoduje, że funkcja zawsze zwraca tę samą wartość.

Pamiętaj, że można to zrobić na dowolnym poziomie abstrakcji. Na przykład możesz przetestować cały algorytm dopasowania wzorca lub przetestować funkcję, która oblicza odległość między dwoma wynikami w procesie optymalizacji. Najpierw zacznij od obszarów, które są najbardziej istotne dla twoich wyników i / lub części kodu, które najbardziej Cię interesują.

Ułatw dodawanie nowych przypadków testowych, rozważ dodanie funkcji „pomocnika” i efektywnie uporządkuj dane wejściowe. Może to oznaczać zapisanie danych wejściowych do pliku, aby można było łatwo ponownie uruchomić testy, ale należy bardzo uważać, aby uniknąć fałszywych wyników pozytywnych lub stronniczych / trywialnie rozwiązanych przypadków testowych.

Zastanów się nad użyciem czegoś takiego jak cross-validation , zobacz ten post na cross- validation , aby uzyskać więcej informacji.

Użyj kontroli wersji

Polecam korzystanie z kontroli wersji i hosting repozytorium na stronie zewnętrznej. Istnieją witryny, które będą hostować repozytoria za darmo.

Zalety:

  1. Zapewnia kopię zapasową na wypadek awarii dysku twardego
  2. Zapewnia historię, która nie pozwala się martwić, czy ostatnio pojawiający się problem został spowodowany przez przypadkową zmianę pliku, a także inne korzyści.
  3. Pozwala na użycie rozgałęzień, co jest dobrym sposobem na pracę nad długoterminowym / eksperymentalnym kodem bez wpływu na niepowiązaną pracę.

Zachowaj ostrożność podczas kopiowania / wklejania kodu

Styl mojego kodu jest zawiły i jest wypełniony przestarzałymi komentarzami wskazującymi alternatywne sposoby zrobienia czegoś lub skopiowanymi wierszami kodu.

  • Kopiowanie / wklejanie kodu pozwala zaoszczędzić czas, ale jest to jedna z najniebezpieczniejszych rzeczy, które możesz zrobić, zwłaszcza jeśli jest to kod, którego sam nie napisałeś (np. Jeśli jest to kod od kolegi).

  • Gdy tylko uruchomisz i przetestujesz kod, zalecam bardzo ostrożne przejrzenie go, aby zmienić nazwy zmiennych lub skomentować wszystko, czego nie rozumiesz.



6

Narzędzia handlu są zwykle wymyślane, aby rozwiązać potrzebę. Jeśli potrzebujesz, skorzystaj z narzędzia, jeśli nie, najprawdopodobniej nie musisz.

W szczególności programy naukowe nie są celem końcowym, są środkiem. Piszecie program, aby rozwiązać problem, który macie teraz - nie spodziewacie się, że ten program będzie używany przez innych (i będzie musiał być utrzymany) za dziesięć lat. Samo to oznacza, że ​​nie musisz myśleć o żadnym z narzędzi, które pozwalają obecnemu deweloperowi zapisywać historię innych, takich jak kontrola wersji, lub przechwytywać funkcje kodu, takie jak testy jednostkowe.

Co by ci wtedy przyniosło korzyści?

  • kontrola wersji jest przyjemna, ponieważ pozwala bardzo łatwo wykonać kopię zapasową pracy. Począwszy od 2018 r. Github jest bardzo popularnym miejscem do tego (w razie potrzeby zawsze możesz go przenieść później - git jest bardzo elastyczny). Tanim i prostym substytutem kopii zapasowych są automatyczne procedury tworzenia kopii zapasowych w systemie operacyjnym (Time Machine dla komputerów Mac, rsync dla systemu Linux itp.). Twój kod musi znajdować się w wielu miejscach!
  • Testy jednostkowe są dobre, ponieważ jeśli je najpierw napiszesz, musisz zastanowić się, jak sprawdzić, co faktycznie robi kod, co pomaga zaprojektować bardziej użyteczny interfejs API dla Twojego kodu. Jest to pomocne, jeśli kiedykolwiek zaczniesz pisać kod, który będzie później użyty ponownie, i pomoże ci podczas zmiany algorytmu, ponieważ wiesz, że działa on w tych przypadkach.
  • Dokumentacja. Naucz się pisać odpowiednią dokumentację w używanym języku programowania (na przykład javadoc dla Java). Napisz na przyszłość. W tym procesie przekonasz się, że dobre nazwy zmiennych ułatwiają dokumentowanie. Powtarzać. Dbaj o swoją dokumentację tak samo, jak poeta robi wiersze.
  • Używaj dobrych narzędzi. Znajdź IDE, które Ci pomoże i dobrze się naucz. W ten sposób refaktoryzacja, np. Zmiana nazw zmiennych na lepszą nazwę, jest znacznie łatwiejsza.
  • Jeśli masz rówieśników, rozważ użycie recenzji. Poznanie i zrozumienie Twojego kodu przez osobę z zewnątrz jest obecną wersją przyszłości, dla której piszesz. Jeśli twój rówieśnik nie rozumie twojego kodu, prawdopodobnie nie później.

W jaki sposób ta odpowiedź nie otrzymała opinii? Teraz ma. Nasza grupa uznała recenzowanie za jedno z najskuteczniejszych narzędzi, o wiele ważniejsze niż testy jednostkowe, jeśli chodzi o kod naukowy. Łatwo jest popełnić błąd, tłumacząc złożony zestaw równań w artykule w czasopiśmie naukowym na kod. Naukowcy i inżynierowie często tworzą wyjątkowo biednych programistów; wzajemna ocena może wychwycić architektoniczne firmy, które utrudniają utrzymanie / zrozumienie / użycie kodu.
David Hammen

5

Oprócz dobrych rad już tutaj, możesz rozważyć cel swojego programowania, a zatem to, co jest dla Ciebie ważne.

„Chodzi o matematykę lub naukę, które testuję lub badam w ramach programowania”.

Jeśli celem jest eksperymentowanie i testowanie czegoś dla własnego zrozumienia, a wiesz, jakie powinny być wyniki, twój kod jest w zasadzie szybkim wyrzuceniem, a twoje obecne podejście może wystarczyć, choć może być ulepszone. Jeśli wyniki nie są zgodne z oczekiwaniami, możesz wrócić i sprawdzić.

Jeśli jednak wyniki kodowania informują o kierunku badań i nie wiesz, jakie powinny być wyniki, poprawność staje się szczególnie ważna. Błąd w kodzie może prowadzić do wyciągania błędnych wniosków z eksperymentu, co ma różne złe implikacje dla ogólnych badań.

W takim przypadku rozbicie kodu na łatwo zrozumiałe i weryfikowalne funkcje za pomocą testów jednostkowych da ci solidniejsze klocki konstrukcyjne, dające większe zaufanie do twoich wyników i może uratować cię od dalszej frustracji.


5

Mimo że kontrola wersji i testy jednostkowe służą do utrzymania uporządkowanego i funkcjonalnego kodu, nie pomagają w pisaniu czystszego kodu.

  • Kontrola wersji pozwoli ci zobaczyć, jak i kiedy kod stał się tak niechlujny, jak jest.
  • Testy jednostkowe upewnią się, że mimo że kod jest kompletnym bałaganem, nadal działa.

Jeśli chcesz powstrzymać się od pisania niechlujnego kodu, potrzebujesz narzędzia, które działa tam, gdzie zdarzają się bałagany: podczas pisania kodu. Popularny rodzaj narzędzia, które to robi, nazywa się linter. Nie jestem programistą python, ale wygląda na to, że Pylint może być dobrym rozwiązaniem.

Liniowiec sprawdza napisany kod i porównuje go z konfigurowalnym zestawem najlepszych praktyk. Jeśli linijka ma regułę, że zmienne muszą być camelCase, a ty ją zapiszesz snake_case, oznaczy to jako błąd. Dobre lintery mają reguły od „deklarowanych zmiennych należy użyć” do „Cyklomatyczna złożoność funkcji musi być mniejsza niż 3”.

Większość edytorów kodu można skonfigurować tak, aby uruchamiały linijkę za każdym razem, gdy zapisujesz, lub po prostu podczas pisania, i wskazywały na problemy. Jeśli wpiszesz coś podobnego x = 7, xzostanie podświetlone, wraz z instrukcją użycia dłuższej, lepszej nazwy (jeśli tak skonfigurowałeś). Działa to jak sprawdzanie pisowni w większości edytorów tekstu, co utrudnia ignorowanie i pomaga budować lepsze nawyki.


To powinno mieć znacznie więcej pozytywnych opinii. +1
wrzos

2
Ale, na litość boską, upewnij się, że wiesz, jak skonfigurować podszewkę w stylu, który ci się podoba, w przeciwnym razie doprowadzi cię do szaleństwa z powodu zamieszania.
DrMcCleod

4

Wszystko, co wymieniłeś, jest narzędziem w metaforycznym zestawie narzędzi. Jak wszystko w życiu, różne narzędzia są odpowiednie do różnych zadań.

W porównaniu z innymi dziedzinami inżynierii oprogramowanie działa z wieloma pojedynczymi elementami, które same w sobie są dość proste. Instrukcja przypisania nie ocenia się inaczej w zależności od wahań temperatury w pomieszczeniu. ifOświadczenie nie koroduje na miejsce i wracać to samo po pewnym czasie. Ale ponieważ poszczególne elementy są tak proste, a oprogramowanie stworzone przez ludzi, elementy te są łączone w coraz większe kawałki, aż wynik będzie tak duży i złożony, że osiągnie granice tego, co ludzie mogą mentalnie zarządzać.

W miarę rozwoju projektów komputerowych ludzie je badali i tworzyli narzędzia do zarządzania tą złożonością. OOP jest jednym z przykładów. Coraz więcej abstrakcyjnych języków programowania to kolejny sposób. Ponieważ znaczna część pieniędzy na oprogramowanie robi więcej więcej , narzędzia do osiągnięcia tego są tym, co zobaczycie i przeczytacie. Ale wygląda na to, że te sytuacje cię nie dotyczą.

Więc nie myśl, że musisz to robić. Pod koniec dnia kod jest tylko środkiem do celu. Niestety, najlepszą perspektywą na to, co jest, a co nie jest właściwe, jest praca nad niektórymi większymi projektami, ponieważ o wiele trudniej jest wiedzieć, czego brakuje, gdy zestaw narzędzi jest twoim umysłem.

W każdym razie nie martwiłbym się, że nie użyję OOP ani innych technik, o ile twoje skrypty są małe. Wiele opisanych przez ciebie problemów to tylko ogólne profesjonalne umiejętności organizacyjne, tzn. Nietracenie starego pliku jest czymś, z czym muszą sobie poradzić wszystkie dziedziny.


4

Oprócz wszystkich dobrych podanych dotychczas sugestii, jedną z praktyk, których nauczyłem się z czasem i które uważam za niezbędne, jest bardzo swobodne dodawanie szczegółowych komentarzy do kodu. Jest to dla mnie najważniejsza rzecz, kiedy wracam do czegoś po długim czasie. Wyjaśnij sobie, co myślisz. Zajmuje to trochę czasu, ale jest stosunkowo łatwe i przeważnie bezbolesne.

Czasami mam dwa lub trzy razy więcej wierszy komentarzy niż w kodzie, szczególnie gdy koncepcje lub techniki są dla mnie nowe i nie potrafię się wytłumaczyć.

Wykonuj kontrolę wersji, popraw swoje praktyki itp. ... wszystkie powyższe. Ale wytłumacz sobie wszystko, kiedy będziesz. Działa naprawdę dobrze.


4

Jakie cechy są ważne dla tego rodzaju programu?

Prawdopodobnie nie ma znaczenia, czy łatwo go utrzymać, czy rozwinąć, ponieważ są szanse, że tak się nie stanie.

Prawdopodobnie nie ma znaczenia, jak wydajna jest.

Prawdopodobnie nie ma znaczenia, czy ma świetny interfejs użytkownika, czy jest zabezpieczony przed złośliwymi atakami.

Może mieć znaczenie, że jest czytelny: że ktoś czytający Twój kod może łatwo przekonać się, że robi to, co twierdzi.

To na pewno ma znaczenie, że jest poprawne. Jeśli program daje niepoprawne wyniki, to twoje naukowe wnioski wyskakują z okna. Ale musi tylko poprawnie przetworzyć dane wejściowe, o które tak naprawdę prosisz; naprawdę nie ma większego znaczenia, czy przewróci się, jeśli otrzyma ujemne wartości danych wejściowych, czy wszystkie wartości danych są dodatnie.

Ważne jest również to, że utrzymujesz pewien poziom kontroli zmian. Twoje wyniki naukowe muszą być powtarzalne, a to oznacza, że ​​musisz wiedzieć, która wersja programu dała wyniki, które zamierzasz opublikować. Ponieważ jest tylko jeden programista, kontrola zmian nie musi być bardzo skomplikowana, ale musisz upewnić się, że możesz wrócić do punktu w czasie i odtworzyć swoje wyniki.

Więc nie martw się o paradygmaty programowania, orientację obiektową, elegancję algorytmiczną. Nie martw się o przejrzystość i czytelność oraz o możliwość śledzenia zmian w czasie. Nie martw się o interfejs użytkownika. Nie przejmuj się testowaniem każdej możliwej kombinacji parametrów wejściowych, ale wykonaj wystarczająco dużo testów, aby mieć pewność (i aby inni byli pewni), że twoje wyniki i wnioski są prawidłowe.


4

Pracowałem w podobnym środowisku z naukowcami, którzy piszą dużo kodu (matematyki / nauk ścisłych), ale ich postęp jest powolny z tych samych powodów, które opisujesz. Zauważyłem jednak jedną konkretną rzecz, która poszła dobrze, która moim zdaniem może ci również pomóc: zbudować i utrzymywać zbiór specjalistycznych bibliotek, których można używać w wielu projektach. Te biblioteki powinny zapewniać funkcje narzędziowe, dzięki czemu Twój projekt będzie specyficzny dla problematycznej domeny.

Na przykład, być może będziesz musiał poradzić sobie z wieloma transformacjami współrzędnych w twojej dziedzinie (ECEF, NED, lat / lon, WGS84 itp.), Więc funkcja taka convert_ecef_to_ned()powinna przejść do nowego projektu o nazwie CoordinateTransformations. Poddaj projekt kontroli wersji i umieść go na serwerach swojego działu, aby inne osoby mogły go używać (i mam nadzieję, że go ulepszą). Następnie po kilku latach powinieneś mieć solidny zbiór bibliotek z projektami zawierającymi tylko kod specyficzny dla konkretnego problemu / dziedziny badań.

Kilka bardziej ogólnych porad:

  • Zawsze staraj się modelować konkretny problem tak dokładnie, jak to możliwe, bez względu na to, co to jest. W ten sposób pytania dotyczące projektowania oprogramowania, takie jak: co / gdzie / jak umieścić zmienną, powinny stać się o wiele bardziej oczywiste.
  • Nie zawracałbym sobie głowy rozwojem opartym na testach, ponieważ kod naukowy opisuje pomysły i koncepcje i jest bardziej kreatywny i płynny; nie ma interfejsów API do zdefiniowania, usług do utrzymania, zagrożeń dla kodu innych osób przy zmianie funkcjonalności itp.

Nie pozwól innym ludziom go poprawić. Możliwe, że nie rozumieją celu kodu i po prostu popsują sprawę.
Mathreadler

@mathreadler Cóż, jeśli są to ogólne biblioteki narzędziowe, inni będą mieli problem z zepsuciem - taki jest pomysł.
jigglypuff

Dlaczego trudno jest zepsuć biblioteki ogólnego przeznaczenia? Nie jest to takie trudne, jeśli nie masz pojęcia, co robisz, lub jeśli naprawdę bardzo się starasz, jeśli o to chodzi.
matreadler

@ matreadler Ponieważ na ogół istnieje tylko jeden sposób na koordynację przekształceń lub konwersji jednostek.
jigglypuff

Zwykle istnieje wiele sposobów, w zależności od tego, jak przechowywane są liczby w pamięci, jakiej reprezentacji używają i wielu innych rzeczy, dla których procesorów zamierzasz skompilować bibliotekę. Jeden koder może założyć, że każdy na przykład zawsze będzie korzystał z dubletów IEEE, ale inny prawie zawsze używa pojedynczej precyzji lub jakiegoś trzeciego bardziej dziwnego formatu. Jeden koder użyje wtedy polimorfizmu szablonu, ale inny może być na niego uczulony, a trzeci nawet jeszcze dziwniejszy na sztywno koduje wszystko na niskim poziomie c lub asemblerze.
matematyk

3

Oto moje opinie i bardzo duży wpływ na moją własną ścieżkę.

Kodowanie często rodzi dogmatyczne perspektywy, w jaki sposób należy robić rzeczy. Zamiast technik i narzędzi, myślę, że musisz spojrzeć na skumulowane wartości i koszty, aby wybrać odpowiednią strategię.

Pisanie dobrego, czytelnego, debugowalnego, solidnego kodu zajmuje dużo czasu i wysiłku. W wielu przypadkach, biorąc pod uwagę ograniczony horyzont planowania, nie warto tego robić (paraliż analizy).

Jeden kolega miał ogólną zasadę; jeśli robisz zasadniczo to samo po raz trzeci, zainwestuj wysiłek, w przeciwnym razie właściwa jest szybka i brudna robota.

Testowanie jest niezbędne, ale w przypadku jednorazowych projektów wystarczy samo spojrzenie w oczy. Do wszystkiego, co istotne, niezbędne są testy i infrastruktura testowa. Wartość polega na tym, że uwalnia cię podczas kodowania, a koszt polega na tym, że jeśli test koncentruje się na konkretnej implementacji, wówczas testy również wymagają konserwacji. Testy przypominają również o tym, jak mają działać rzeczy.

W przypadku moich własnych skryptów jednorazowych (często takich jak sprawdzanie poprawności prawdopodobieństwa lub podobnych) znalazłem dwie małe rzeczy bardzo przydatne: 1. Dołącz komentarz pokazujący, w jaki sposób kod jest używany. 2. Dołącz krótki opis, dlaczego napisałeś kod. Te rzeczy są strasznie oczywiste, gdy piszesz kod, ale oczywistość marnuje się z czasem :-).

OOP polega na ponownym użyciu kodu, abstrakcji, enkapsulacji, faktorowaniu itp. Bardzo przydatne, ale łatwo się zgubić, jeśli tworzenie wysokiej jakości kodu i projektu nie jest twoim ostatecznym celem. Produkcja wysokiej jakości produktów wymaga czasu i wysiłku.


3

Chociaż uważam, że testy jednostkowe mają swoje zalety, mają one wątpliwą wartość dla rozwoju naukowego - często są po prostu zbyt małe, aby oferować dużą wartość.

Ale naprawdę lubię testy integracyjne kodu naukowego:

Wyizoluj małą część kodu, która mogłaby działać samodzielnie, np. Potok ETL. Następnie napisz test, który dostarcza danych, uruchom potok etl (lub tylko krok), a następnie sprawdź, czy wynik odpowiada twoim oczekiwaniom. Podczas gdy testowana porcja może zawierać dużo kodu, test wciąż zapewnia wartość:

  1. Masz wygodny haczyk do ponownego uruchomienia kodu, który pomaga często go uruchamiać.
  2. Możesz przetestować niektóre założenia w swoim teście
  3. Jeśli coś się zepsuje, łatwo jest dodać test zakończony niepowodzeniem i wprowadzić poprawkę
  4. Kodyfikujesz oczekiwane wejścia / wyjścia, unikając zwykłego bólu głowy wynikającego z próby odgadnięcia formatu danych wejściowych.
  5. Chociaż testy IT nie są tak ubogie, jak testy jednostkowe, nadal pomagają rozdzielić kod i zmusić Cię do dodania pewnych granic w kodzie.

Często używam tej techniki i często uzyskuję względnie czytelną funkcję główną, ale podfunkcje są często dość długie i brzydkie, ale można je szybko modyfikować i zmieniać z powodu solidnych granic we / wy.


2

Zwykle pracuję na bardzo dużej bazie. Używamy wszystkich narzędzi, o których wspomniałeś. Ostatnio zacząłem pracować nad niektórymi skryptami Pythona dla pobocznego projektu. Są to najwyżej od kilkudziesięciu do kilkuset linii. Z przyzwyczajenia oddałem swoje skrypty do kontroli źródła. Jest to przydatne, ponieważ mogę tworzyć gałęzie do testowania eksperymentów, które mogą nie działać. Mogę rozwidlać, jeśli muszę powielić kod i zmodyfikować go w innym celu. To pozostawia oryginał taktowny na wypadek, gdy będę musiał go ponownie wydać.

Do „testów jednostkowych” mam tylko kilka plików wejściowych, które mają na celu wygenerowanie znanego wyjścia, które sprawdzam ręcznie. Prawdopodobnie mógłbym to zautomatyzować, ale wydaje mi się, że zajęłoby to więcej czasu niż zaoszczędziłbym, robiąc to. Prawdopodobnie zależy to od tego, jak często muszę modyfikować i uruchamiać skrypty. Tak czy inaczej, jeśli to działa, zrób to. Jeśli to więcej kłopotów niż jest warte, nie marnuj czasu.


2

W przypadku pisania kodu - podobnie jak w przypadku pisania w ogólności - główne pytanie brzmi:

Który czytelnik masz na myśli? lub Kto zużywa twój kod?

Rzeczy, takie jak formalne wytyczne kodowania, nie mają sensu, gdy jesteś jedynym odbiorcą.

Z drugiej strony pomocne byłoby napisanie kodu w taki sposób, aby Twoja przyszłość była w stanie go zrozumieć od razu.

Tak więc „dobry styl” byłby tym, który najbardziej ci pomoże. To, jak ten styl powinien wyglądać, to odpowiedź, której nie mogę udzielić.

Myślę, że nie potrzebujesz testów OOP ani testów jednostkowych dla plików 150 LOC. Dedykowany VCS byłby interesujący, jeśli masz jakiś ewoluujący kod. W przeciwnym razie a .bakrobi lewę. Te narzędzia są lekarstwem na chorobę, której możesz nawet nie mieć.

Być może powinieneś napisać kod w taki sposób, aby nawet czytając go w stanie nietrzeźwym, potrafiłeś go czytać, rozumieć i modyfikować.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.