6D: Projektowanie debugowanie i pączki

Ta lekcja zawiera pewne sugestie dotyczące łatwiejszego projektowania i debugowania programów. Im dłużej będziesz programować, tym łatwiej będziesz unikać błędów i je naprawiać, ale spróbujemy podać tutaj bardzo ogólne i przydatne porady. Wytłumaczymy następujące techniki:

  • budowanie tylko jednej część naraz
  • rozwiązywanie kilku przykładów ręcznie
  • planowanie kodu przed rozpoczęciem jego pisania
  • zapisywanie kodu
  • testowanie przykładów, które zostały rozwiązane ręcznie
  • testowanie dodatkowych przykładów, w tym przypadki "graniczne" i losowe

Ta magiczna recepta nie rozwiąże wszystkiego automatycznie, ale może zaoszczędzić ci bólu głowy i zmniejszyć liczbę problemów, z którymi zetkniesz się później.

Gdy masz problem, pewne strategie są pomocne w debugowaniu używając instrukcji drukowania diagnostycznego (będziemy to robić w czasie późniejszych lekcji), używając wizualizatora, czytając kod i pisząc dobre testy. Jeśli uruchamiasz Pythona w domu, możesz użyć narzedzi breakpoint i step tools by lepiej zrozumieć, co się nie powiodło.

Algorytm oznacza ciąg instrukcji. Chociaż, program komputerowy, aby działać, musi być napisany w określonym języku, takim jak Python czy JavaScript, to słowo algorytm oznacza instrukcje krok po kroku, które można napisać w języku polskim lub przedstawić z uzyciem schematu blokowego. Na przykład przepis na pączki to algorytm, w którym "mierzenie 3 szklanek mąki" jest jednym krokiem, a "niech ciasto rośnie przez 2 godziny" to kolejny krok. Na tej lekcji opracujemy prosty algorytm. Następnie algorytm zaimplementujemy, co oznacza przekształcimy go w działający program komputerowy.

Problem: Kalkulator pączków

Tim Horton's, sieć sklepów z kawą w Kanadzie, sprzedaje timbity, które są smacznymi pączkowymi kulkami:

Photo: looking_and_learning, flickr

Masz zorganizować kilka wieczorków w ciągu najbliższych kilku tygodni i będziesz zamawiał na każde ze spotkań inną liczbę timbitów. Tak więc, aby zaplanować budżet, chciałbyś, aby program obliczył koszt dowolnej liczby timbitów. W lokalnej filii są następujące ceny:

Liczba Cena
1 0,20$
10 (małe pudełko) 1,99$
20 (średnie pudełko) 3,39$
40 (duże pudełko) 6,19$

Zrób jedną część naraz

Ostatecznie chcesz napisać program, w którym dane wejściowe to P liczba osób na imprezie, a wyjściowe to koszt poczęstunku dla wielu osób wraz z podatkiem. Na najwyższym poziomie są trzy elementy tego programu:

  1. obliczyć, jaka jest T liczba timbitów jaką należy kupić dla P ludzi?
  2. obliczyć kwotę za którą można kupić dokładnie T timbitów
  3. obliczyć podatek i całkowity koszt.

Można jednocześnie pracować nad tymi trzema częściami. Rekomendujemy jednak pracę nad każdą z części oddzielnie, jej testowanie i modyfikowanie przed przystąpieniem do pracy przy następnej części, ponieważ im większy jest Twój program, tym trudniej jest znaleźć i naprawić poszczególne błędy. Łatwiej rozwijać i testować trzy części oddzielnie przed połączeniem ich w jeden program. Przez resztę tej lekcji skoncentrujemy się na kroku 2: obliczyć cenę, za którą kupimy odpowiednią ilość T timbitsów.

Rozwiąż niektóre przykłady ręcznie

Aby nakazać komputerowi coś zrobić, musisz być w stanie zrobić to samodzielnie. Przyjrzyjmy się przykładowi: jaki jest koszt zakupu dokładnie 45 timbitów? Patrząc na listę cen zależną od ilości nabywanych timbitsów, możemy kupić jedno duże pudełko (40), które kosztuje 6,19 USD. Pozostanie 45 - 40 = 5 timbitów, które musimy kupić osobno, co stanowi dodatkowe koszty 5 * 0,20 USD = 1,00 USD. Łączny koszt wynosi 6,19 USD + 1,00 USD = 7,19 USD.

Przed rozpoczęciem pisania zaplanuj swój kod

Co zrobić, jeśli chcemy kupić 4 lub 456 timbits zamiast 45? Ogólnie rzecz biorąc, powinniśmy kupić tyle dużych pudełek, ile jest to możliwe, a następnie średnich i / lub małych pudełek i w razie potrzeby pojedynczych timbitów tyle, aby uzyskać całkowitą sumę.

Czy ten algorytm jest naprawdę optymalny? Tak! Można zauważyć, że kupno dwóch średnich pudełek (2 * 3,39 zł = 6,78 zł) to zły pomysł, ponieważ 2 x 20 = 40 timbitów możemy nabyć taniej w jednym dużym pudełku. Podobne wypada porównanie indywidualnych i małych oraz małych i średnich, można to wykorzystać do udowodnienia, że algorytm daje najniższą cenę kupna określonej liczby timbitów. Jeśli realacje ceny do rozmiaru były inne, to musieliśmy być bardziej ostrożni.

Powiedzieliśmy, że przed rozpoczęciem pisania kodu należy go zaplanować. Możesz:

  • podać krótki krok po kroku opis algorytmu w języku polskim zamiast w Pythonie
  • użyć diagramu lub schematu blokowego, aby wyświetlić kroki
  • skupić się na głównych pomysłach, a nie na szczegółach.

Dla naszego przykładu podajemy opis w języku polskim. Nie pokazujemy Ci schematu blokowego dla tego konkretnego programu, ponieważ będzie on przebiegał z kroku 1 do kroku 2 i tak dalej w prostej linii. Przy programach z pętlami, jak zobaczymy później, bardziej są przydatne diagramy.

Algorytm oblicza koszt dowolnej liczby timbitów

  1. uzyskać dane wejściowe: liczbę timbitów
  2. śledzić całkowity koszt, zaczynając od zera
  3. jeśli możesz, kup jak najwięcej dużych pudełek
    1. obliczyć, ile jeszcze potrzebujesz timbitów
    2. zaktualizować całkowity koszt
  4. jeśli możesz, kup jak najwięcej średnich pudełek i powtórz kroki A. i B
  5. jeśli możesz, kup jak najwięcej małych pudełek i powtórz kroki A. i B
  6. kup pojedyncze timbity i powtórz krok B
  7. wyprowadź całkowity koszt

Ten opisem algorytmu nazywamy pseudokodem, ponieważ jest to seria instrukcji krok po kroku, ale nie w prawdziwym języku programowania.

Zapisz kod

Powtarzając pierwszą część porad, buduj tylko jedną część naraz, możesz również pomyśleć o pisaniu osobnego fragmentu kodu dla każdego etapu od 1 do 7. Dodatkowo, oto jedna rzecz, o której musimy myśleć dla wszystkich kroków: Jakie zmienne będziemy używać? Jakie będą ich nazwy, typy i wartości początkowe?

Użyjemy timbitsLeft do reprezentowania liczby timbitów, jakie musimy jeszcze kupić, totalCost użyjmy do przedstawienia całkowitego kosztu. Nie musimy wcześniej ustalać typów danych dla Pythona, ale możemy sobie wyobrazić, że timbitsLeft będzie int, a totalCost będzie zmienną typu float mierzącą całkowity koszt w dolarach. (Jeden cent to 0,01 dolara). W trakcie będziemy potrzebować dodatkowych tymczasowych zmiennych, którymi zajmiemy tylko w razie potrzeby.

Kroki 1 i 2 można zapisać jednym wierszem kodu:

timbitsLeft = int(input()) # krok 1: uzyskać dane wejściowe
totalCost = 0              # krok 2: nadać zmiennej totalCost wartość początkową
W kroku 3 musimy obliczyć maksymalną liczbę dużych pudełek, które można kupić. Jak dokładnie możemy to zrobić? Ponieważ duże pudełko zawiera 40 timbitów, liczba bigBoxów powinna być liczbą całkowitą uzyskaną z dzielenia timbitsLeft przez 40. Śledząc dzielenie, koszty i odejmowanie, otrzymujemy kolejny fragment kodu:

# krok 3: kupić, ile się da, dużych pudełek
bigBoxes = timbitsLeft / 40
totalCost = totalCost + bigBoxes * 6.19    # zaktualizować całkowity koszt
timbitsLeft = timbitsLeft - 40 * bigBoxes  # obliczyć, ile jeszcze trzeba kupić 
Wyobraźcie sobie, że już popełniliśmy błąd. Najlepszym sposobem na wyłapanie błędów jest od samego początku testowanie kodu i robienie tego często! Mówiąc optymistycznie, w tym momencie przetestowaliśmy kod do tego punktu. Wystarczy dodać kilka poleceń wydruku, aby obserwować jego dotychczasowe zachowanie. (Aby sprawdzić każdy krok, zamiast używać instrukcji drukowania, możemy również użyć wizualizatora).

Przykład
Testowanie kodu w celu debugowania przy danych wejściowych 45 z użyciem instrukcji print.

To nie działa tak jak chcieliśmy! W czym problem? Która linia jako pierwsza jest inna od tego co planowaliśmy? Spróbuj zlokalizować ją przed otwarciem następnego akapitu. Pamiętaj, że wcześniej rozwiązaliśmy ten przykład ręcznie.

Kliknij, aby przeczytać wyjaśnienie.
Sprawcą jest linia bigBoxes = timbitsLeft / 40, ponieważ w bigBoxes otrzymujemy 1,125, a spodziewaliśmy się otrzymać 1. Nie można kupić ułamka pudełka. Jednym z możliwych obejść, które będzie można zobaczyć w lekcji 7A, jest użycie operatora dzielenia bez reszty // (odrzuca cząść ułamkową i zaokrągla wynik do najbliższej liczby całkowitej z lewej strony na osi liczbowej) zamiast / zwykłego dzielenia. Innym rozwiązaniem, opierającym się na tym, co widzieliśmy w ćwiczeniu 4, jest przekonwertowanie timbitsLeft / 40 do int, co usunie część ułamkową. Użyjemy tego podejścia: linia zmieni się bigBoxes = int(timbitsLeft / 40).

Kroki 4 i 5 są podobne do kroku 3. Alternatywnie, ponieważ kupisz co najwyżej jedno średnie i co najwyżej jedno małe pudełko, możemy użyć instrukcji if:

if timbitsLeft >= 20: # step 4, czy możemy kupić średnie pudełko?
  totalCost = totalCost + 3.39
  timbitsLeft = timbitsLeft - 20
if timbitsLeft >= 10: # step 5, czy możemy kupić małe pudełko?
  totalCost = totalCost + 1.99
  timbitsLeft = timbitsLeft - 20
Aby zakończyć, musimy zapłacić 20 centów za każde z ostatnich potrzebnych timbitów i wyprowadzić wynik.

totalCost = totalCost + timbitsLeft * 20 # krok 6
print(totalCost)                         # krok 7

Sprawdź przykłady, które zostały rozwiązane ręcznie

Oto cały program. Pierwszą rzeczą, którą zrobimy to sprawdzenie, czy działa on przy danych wejściowych 45, dla których wykonaliśmy obliczenia ręcznie.

Przykład
Jaka jest cena za 45 timbitów?
Możesz wprowadzić dane dla programu w poniższym polu.

Musi być błąd, ponieważ płacimy ponad 100 dolarów za 45 timbitów! Twoim końcowym zadaniem jest znalezienie i wyeliminowanie tego błędu oraz jeszcze innego błędu ukrytego w kodzie. Zostanie on przetestowany w następnej sekcji.

Więc już wiesz, że zabawna rzecz dzieje się dzieje z ceną, która nie jest błędem: może okazać się tańszy zakup dokładnie T + 1 timbitów niż zakup dokładnie T timbitów. Na przykład zakup dokładnie 19 timbitów kosztuje 3,79 USD, ale zakup dokładnie 20 timbitów kosztuje tylko 3,39 USD. Niezależnie od tych spostrzeżeń, będziemy trzymać się zasady kupowania dokładnie T timbów tak tanio, jak to tylko możliwe i algorytm wyjaśniony wcześniej jest poprawny. Więc twój program powinien wygenerować wynik 3,79, gdy na wejściu podamy 19.

Sprawdź dodatkowe przykłady

W Computer Science Circles staramy się inteligentnie zautomatyzować testowanie kodu w celu upewnienia się, że rozwiązałeś dany problem prawidłowo. Ale w prawdziwym testowaniu programu, na tobie spoczywa obowiązek sprawdzenia własnego kodu. Oto kilka przydatnych wskazówek do testowania.

  • Sprawdzić, czy każda linia kodu programu działa prawidłowo. W problemie z timbitami, gdy na wejściu jest 10 sprawdzisz, czy prawidłowo obsługiwane są małe pudełka. Podobnie, gdy na wejściu masz 20 i 40 sprawdzisz, czy średnie i duże pola są prawidłowo obsługiwane. Na koniec sprawdź, czy prawidłowo program działa dla pojedynczych timbitów (np. na wejściu 1).
    • To może pomóc w sprawdzeniu, czy poprawnie wpisywano wartości, takie jak 3,39 USD i 1,99 USD.
  • Sprawdź przypadki "graniczne", które mogą spychać program do jego granic. Na przykład minimalne możliwe wejście dla tego programu to 0, ponieważ nie można mieć ujemnych timbitów. Nasz program dla danych wejściowych nie ma ograniczenia górnego, ale warto spróbować takich wartości jak 39 i 41, które są bliskie granicy kupowania jednego dużego pudełka.

    Może to pomóc w sprawdzeniu, czy przypadkowo nie wpisałeś> kiedy powinieneś wpisać> = zamiast tego.

    • To pomoże w sprawdzenie, czy przypadkiem nie wpisałeś > zamiast >=.
  • Aby sprawdzić, czy program działa poprawnie w niektórych przypadkach można automatycznie sprawdzić każde wejście lub sprawdzić dużą liczbę losowych wejść. W testach wewnętrznych CS Circles używamy losowych danych wejściowych. Użyj na początku swojego programu import random a następnie na przykład random.randint (10, 100), co wygeneruje losową liczbę całkowitą między 10 a 100.

Czy możesz znaleźć oba błędy i zaliczyć wszystkie testy? Spróbuj!

Niektóre obliczenia powodują bardzo małe błędy w Pythonie:
Przykład: Błąd zaokrąglenia
To zaskakujące, że kod powyżej nie daje dokładnie 0,35! Ma to związek z tym, jak Python przechowuje informacje. Nie przejmuj się takimi drobnymi błędami: nasz automatyczny tester ignoruje je. Na ten temat powiemy więcej w lekcji 7B.

Zadanie na kodowanie : Timbity
Napraw błędy i przejdź wszystkie testy.

Głodny? Jesteś gotowy by kontynuować i przejść do następnej lekcji!

Podczas, gdy 10 timbitów daje jedno pudełko, to 8 timbitów daje jeden timbyte. Photo: thisisbrianfisher, flickr