Spustit jako prezentaci

Refaktoring, pseudokód

Doporučené postupy v programování - cvičení

MFF UK

Na rozehřátí - kód z předpeklí (C)

Co to dělá?

if (
        ((strcmp(argv[i],"-f") == 0) && (i++) && ((shift=0) || 1))
        ||
        ((strncmp(argv[i],"--file=", 7) == 0) && (shift=7))
        ) {
    if (i >= argc) { /* Error. */ }
    char *input = argv[i] + shift;
    /* .. */
}
				

Nápověda

  • je to zpracování parametrů na příkazové řádce
  • ten záhadný argument shift je použit pro posun v řetězci
    • kvůli --file=soubor.txt
    • a -f soubor.txt
  • tohle jsem opsal ze svého (VH) programu starého několik let

Na rozehřátí - kód z předpeklí (C)

Řešení

Jde o (velmi nepodařené) zpracovávání argumentů z příkazové řádky.

Cílem je, aby program dokázal přijmout název vstupního souboru specifikovaného jako -f soubor.txt nebo --file=soubor.txt.

Korektní řešení je v tomto případě použít knihovnu getopt popř. nějakou její náhradu.

Refaktoring

Refaktoring - příklady

Cílem je

Příklad - libc.h (C)

Opravte chyby

// Soubor libc.h

void *malloc(const size_t);
void free(const void *);
int thread_create(void (*)(void *), void *, const char *, thread_id_t *);
void thread_exit(int);
void thread_detach(thread_id_t);
int thread_join(thread_id_t);
void *calloc(const size_t, const size_t);
void *memalign(const size_t, const size_t);
void *realloc(const void *, const size_t);
thread_id_t thread_get_id(void);
				
  • chybou je promíchání dvou funkčně různých skupin
  • chybějící názvy parametrů -- námět na diskuzi, zde to není hlavní problém

Příklad - libc.h (C)

// Soubor libc.h

void *malloc(const size_t);
void free(const void *);
int thread_create(void (*)(void *), void *, const char *, thread_id_t *);
void thread_exit(int);
void thread_detach(thread_id_t);
int thread_join(thread_id_t);
void *calloc(const size_t, const size_t);
void *memalign(const size_t, const size_t);
void *realloc(const void *, const size_t);
thread_id_t thread_get_id(void);
				

Příklad - libc.h (C)

Řešení

Funkce pro práci s vlákny

// Soubor thread.h
int thread_create(void (*)(void *), void *, const char *, thread_id_t *);
void thread_exit(int)
void thread_detach(thread_id_t);
int thread_join(thread_id_t);
thread_id_t thread_get_id(void);
				

Funkce pro práci s dynamickou pamětí

// Soubor malloc.h
void *malloc(const size_t size);
void *calloc(const size_t nmemb, const size_t size);
void *memalign(const size_t align, const size_t size);
void *realloc(const void *addr, const size_t size);
void free(const void *addr);
				

Příklad - tisk skóre hráčů (C#)

Zadání (část I)

class Score
{
    public string Player => /*...*/;
    public int Score => /*...*/;
}
				

Příklad - tisk skóre hráčů (C#)

Zadání (část II)

class Game
{
    private const string Sep1 = ".\t";
    private const string Sep2 = "\t";

    /* ... */

    public void PrintHiscore(Score[] scores)
    {
        for (int scoreIndex = 0; scoreIndex < scores.Length; scoreIndex++)
        {
            Console.WriteLine(
                (scoreIndex + 1)
                + Sep1
                + scores[scoreIndex].Player
                + Sep2
                + scores[scoreIndex].Score
            );
        }
    }
}
				
  • Sep1 a Sep2 jsou platné v celé třídě
    • ačkoliv se použijí v jediné metodě (ale proč ne)
    • a mají neříkající název
    • navíc je IMHO způsob tištění takhle nějaký podivný
  • proměnná scoreIndex je zbytečně dlouhá
  • proč nejde použít "foreach"?
    • potřebujeme indexovou proměnnou pro tisk pořadí
    • jenže tu tiskneme stejně jako + 1, takže by se možná vyplatilo mít ji bokem

Příklad - tisk skóre hráčů (C#)

Lepší?

class Game
{
    private const string HiscoreIndexPlayerSeparator = ".\t";
    private const string HiscorePlayerScoreSeparator = "\t";

    /* ... */

    public void PrintHiscore(Score[] scores)
    {
        for (int i = 0; i < scores.Length; i++)
        {
            Console.WriteLine(
                (i + 1)
                + HiscoreIndexPlayerSeparator
                + scores[i].Player
                + HiscorePlayerScoreSeparator
                + scores[i].Score
            );
        }
    }
}
				

Příklad - tisk skóre hráčů (C#)

Další iterace...

class Game
{
    // Prints 'index. name score'
    private const string HiscoreFormat = "{0}.\t{1}\t{2}";
    
    /* ... */

    public void PrintHiscore(Score[] scores)
    {
        for (int i = 0; i < scores.Length; i++)
        {
            Console.WriteLine(HiscoreFormat, 
                i + 1, 
                scores[i].Player, 
                scores[i].Score);
        }
    }
}

				

Příklad - tisk skóre hráčů (C#)

Dvě možná řešení...

Klasicky

class Game
{
    private string BuildScoreLine(Score score, int position)
        => $"{position}.\t{score.Player}\t{score.Score}";

    public void PrintHiscore(IEnumerable<Score> scores)
    {
        for (int i = 0; i < scores.Length; i++)
        {
            Console.WriteLine(BuildScoreLine(scores[i], i + 1));
        }
    }
}
									

Příklad - tisk skóre hráčů (C#)

Dvě možná řešení...

Anebo s LINQem

class Game
{
    private string BuildScoreLine(Score score, int position)
        => $"{position}.\t{score.Player}\t{score.Score}";

    public void PrintHiscore(IEnumerable<Score> scores)
    {
        scores
            .Select((score, i) => BuildScoreLine(score, i + 1))
            .ToList()
            .ForEach(Console.WriteLine);
    }
}
									

Příklad - výpočet průměrné rychlosti (Java)

Opravte chyby

double getAverageSpeed(int distance, int time) {
    if (time != 0) {
        return distance / time;
    } else {
        throw new IllegalArgumentException(
        "Parameter time should not be zero.");
    }
}
				
  • ošetřování chyby v else větvi
    • chyby ošetřit nejdříve a hned skončit
    • normální běh -- hlavní větev (neodsazený)

Příklad - výpočet průměrné rychlosti (Java)

double getAverageSpeed(int distance, int time) {
    if (time != 0) {
        return distance / time;
    } else {
        throw new IllegalArgumentException(
        "Parameter time should not be zero.");
    }
}
				

Příklad - výpočet průměrné rychlosti (Java)

Řešení

double getAverageSpeed(int distance, int time) {
    if (time == 0) {
        throw new IllegalArgumentException(
        "Parameter time should not be zero.");
    }

    return distance / time;
}
				

Vsuvka - ošetřování null referencí

public void SendMessage(Message message, User recipient, User sender)
{
    if (message == null || recipient == null)
    {
        throw new ArgumentException("Message and recipient can't be null.");
    }
    /* ... */
  • Většina ošetřovaných vstupů v dnešních programech - null reference
  • Filozofická otázka - k čemu vlastně slouží null reference?
  • Problém null hodnot je, že je potřebujeme vždy typicky na nějakém konkrétním rozhraní, které je schopno indikovat "nepřítomnost" nějakého prvku (nemám data) - nejde typicky o chybový stav.

    Vsuvka - ošetřování null referencí

    Jak víme, kde může null nastat? Potřebujeme informaci v rámci rozhraní, ideálně typového systému. Problém je v tom, že dnešní jazyky neznají koncept referenčního typu, který nepodporuje null hodnoty. Přitom jde o velmi přirozený požadavek, z bodů uvedených na slidu je jasné, že nullabilita by měla být volitelná v rámci specifických rozhraní.

    Tony Hoare (mj. autor quicksortu) null referenci "vynalezl" v roce 1965 při návrhu typového systému pro referenční typy v objektově orientované mutaci ALGOLu, ALGOGL W. Další jazyky potom tento koncept převzaly a stal se z něj více méně standard. Výše uvedený citát je z roku 2009.

    Vsuvka - ošetřování null referencí

    Řešení?

    public void sendMessage(@NotNull Message message,
                            @NotNull User recipient,
                            @Nullable User sender) {
    							
  • Integrovat zákaz null referencí do typového systému
    • Koncept non-nullable a nullable referenčních typů
    • Při deklaraci proměnné či parametru metody manuálně určím zda má být nullable nebo ne
    • Do non-nullable proměnné nikdy nemohu přímo přiřadit výraz nullable typu (tedy null či obsah nullable proměnné)
    • Z nullable výrazu udělám non-nullable buďto null checkem, nebo explicitním unwrapem, což je jediné místo v programu, kde může být vyvolána NullReferenceException
    • Poměrně běžný koncept v nových jazycích (Swift, Kotlin)
    • Klíčová výhoda - null ošetřuji jen tam, kde reálně mohl vzniknout, zbytkem programu propaguji non-nullable hodnoty
  • fun sendMessage(message: Message, recipient: User, sender: User?) {
    							

    Anotace jsou k dispozici v Javě i v C#, ovšem v různých variantách (např. od JetBrains). IDE či pluginy dnes umí nad "svými" anotacemi provést statickou analýzu a identifikovat přiřazení nullable do non-nullable. Vynuceno při kompilaci ovšem není nic. Problémem je také nestandardizovanost.

    Nově vznikající jazyky proto mají přimo v type systému zanesen fakt, že jak hodnotové tak referenční typy mohou být uvedeny jako nullable či non-nullable. Platí to jak pro deklaraci proměnných, tak pro deklaraci parametrů metod. Zdá se, že existuje proposal pro podobnou featuru do jedné z budoucích verzí C# (https://blogs.msdn.microsoft.com/dotnet/2017/11/15/nullable-reference-types-in-csharp/).

    Příklad - úrovně logování (PHP)

    Opravte chyby

    /* Emulate enum for log levels. */
    define("LOG_LEVEL_INFO",  0);
    define("LOG_LEVEL_ERROR", 1);
    
    function log( $level, $message) {
        if ($level == LOG_LEVEL_INFO) {
            $color = "green";
        } else {
            $color = "red";
        }
    
        echo "<p style='color: $color'>"
            . htmlspecialchars($message, ENT_QUOTES)
            . "</p>";
    }
    				
    • emulace konstant v PHP
    • neošetří špatný vstup
    • více se hodí switch
      • vždycky přidat default větev (třeba s assertem)
    • já (VH) bych zase nahradil echo příkazem printf
      • přijde mi kompaktnější
      • lepší kvůli lokalizaci (tady je to jedno, ale...)

    Příklad - úrovně logování (PHP)

    /* Emulate enum for log levels. */
    define("LOG_LEVEL_INFO",  0);
    define("LOG_LEVEL_ERROR", 1);
    
    function log( $level, $message) {
        if ($level == LOG_LEVEL_INFO) {
            $color = "green";
        } else {
            $color = "red";
        }
    
        echo "<p style='color: $color'>"
            . htmlspecialchars($message, ENT_QUOTES)
            . "</p>";
    }
    				

    Příklad - úrovně logování (PHP)

    Řešení

    /* Emulate enum for log levels. */
    define("LOG_LEVEL_INFO",  0);
    define("LOG_LEVEL_ERROR", 1);
    
    function log( $level, $message) {
        switch ($level) {
            case LOG_LEVEL_INFO:
                $color = "green";
                break;
            case LOG_LEVEL_ERROR:
                $color = "red";
                break;
            default:
                assert(false && "Unreachable code");
        }
    
        echo "<p style='color: $color'>"
            . htmlspecialchars($message, ENT_QUOTES)
            . "</p>";
    }
    				
    Poslední detail - ještě bych extrahoval výběr barvy do zvláštní metody (případně dokonce třídy, pod interface apod.). Nyní máme více méně tak dobré řešení, jak PHP dovoluje. Klíčové problémy:
    • Co když někdo předá jinou než definovanou hodnotu level?
    • Co když přidáme další level (např. WARN)?
    Default větev s assertem spadne až za runtimu, a to ještě ne v release módu, takže chování může být různé. Možnosti jak některé jazyky umožňují to řešit uvidíme v dalších slidech.

    Síla příkazu switch

    C/C++, Java

    • jen ordinální typy
    • jen jedna hodnota
    switch (value) {
        case 1:
            xy = 1;
            break;
        case 2:
        case 3:
            xy = 4;
            break;
        default:
            xy = 45;
    }
    								

    Ruby

    • cokoliv s definovaným operátorem ===
    • neomezené množství hodnot
    case inputLine
        when "debug"
            dumpDebugInfo
            dumpSymbols
        when /p\s+(\w+)/
            dumpVariable($1)
        when "quit", "exit"
            exit
        else
            print "Illegal command!"
    end
    								

    Síla příkazu switch

    C#

    switch (o)
    {
        case string s when s.Contains("Fizz") || s.Contains("Buzz"):
            Console.WriteLine(s);
            break;
        case int n when n % 5 == 0 && n % 3 == 0:
            Console.WriteLine("FizzBuzz");
            break;
        case int n when n % 5 == 0:
            Console.WriteLine("Fizz");
            break;
        case int n when n % 3 == 0:
            Console.WriteLine("Buzz");
            break;
        case int n:
            Console.WriteLine(n);
            break;
    }
    								
    C# od verze 7.0 podporuje tzv. pattern matching - rozšířený switch umožňující v podmínce otestovat náležitost objektu k danému typu a zároveň provést bezpečný cast. Velmi vhodné pro klasické situace kde pracujeme s objectem a potřebujeme se rozhodnout podle jeho reálného typu - v tu chvíli nahradí klasické if object is Class kaskády kde uvnitř bloku je třeba přetypovat. Obecně ovšem tento pattern jde proti principům OOP (obcházíme polymorfismus) a neměl by se používat příliš často.

    Síla příkazu switch

    Kotlin, Scala

    enum class LogLevel {
        INFO,
        ERROR
    }
    
    fun getLogColor(level: LogLevel): String = when (level) {
        LogLevel.INFO -> "green"
        LogLevel.ERROR -> "red"
    }
    								
    Některé moderní jazyky vynucují při použití switche jako expression pokrytí všech casů, ovšem v případě, že jsou všechny možnosti kompilátoru dopředu známy (jako např. u enumu), nemusíme vkládat závěrečný default. Tím se zbavíme nutnosti použít assert či vyhodit výjimku v default větvi (viz přednáška). Při změně enumu (např. přidání další hodnoty) potom na všech takovýchto switchích dostaneme compilation error.

    Příklad - tisk předků třídy (Java)

    Opravte chyby

    void printClassParents(Class klass) {
        for (
                Class c = klass.getSuperclass();
                c != null;
                c = c.getSuperclass()) {
            System.out.println(c.getName());
        }
    }
    				

    Tohle je ok.

    Na for-cyklus se dá z pohledu sémantiky koukat dvojím způsobem: jako na cyklus s pevným počtem opakování a nebo na cyklus se strukturou "inicializace, podmínka, krok".

    Uvedený příklad zapadá do obou pohledů. O něco spornější jsou takové případy, které mají potřebnou strukturu, ale nemají pevný počat opakování.

    Příklad - tisk předků třídy (Java)

    Tohle je ok

    void printClassParents(Class klass) {
        for (
                Class c = klass.getSuperclass();
                c != null;
                c = c.getSuperclass()) {
            System.out.println(c.getName());
        }
    }
    				

    Příklad - odstranění tab (C)

    Co je na kódu špatně?

    while ( ( c = getchar() ) != '\t' ) {
      putchar( c );
    }
    				

    Špatně je zde vedlejší efekt přiřazení (resp. znásilnění podmínky k přiřazení).

    Příklad - odstranění tab (C++)

    Co je na kódu špatně?

    Implementace č. 1

    std::string read_data;
    while ( std::getline( input_stream, read_data, '\t' ) ) {
        std::cout << read_data;
    }
    				

    Implementace č. 2

    std::string read_data;
    while ( input_stream.good() ) {
      std::getline( input_stream, read_data, '\t' )
      std::cout << read_data;
    }
    				

    Totéž, opravené

    Mimochodem, CPPReference to v demonstračních příkladech má ještě hnusněji: http://en.cppreference.com/w/cpp/string/basic_string/getline

    Příklad - blacklisting slov (shell)

    Co je na kódu špatně?

    while read LINE ; do
      echo "$LINE" | sed 's/blbec/b---c/'
    done < INPUT_FILE
    				

    Tady je taky vedlejší efekt, ale je to v podstatě idiom, který používají všichni.

    Příklad - odstraňování duplicit v poli (C)

    Opravte chyby

    /* Pro účely úlohy předpokládejme, že "result" ukazuje
       na dostatečně dlouhé pole. */
    unsigned array_remove_duplicite_items(int array[], unsigned length,
            int result[]) {
        int i, j;
        unsigned result_length = 0;
    
        for (i = 0; i < length; i++) {
            int found = FALSE;
            for (j = 0; j < result_length; j++) {
                if (array[i] == result[j]) {
                    found = TRUE;
                    break;
                }
            }
    
            if (!found) {
                result[result_length] = array[i];
                result_length++;
            }
        }
    
        return result_length;
    }
    				
    • označení parametru jako výstupního
      • v C nelze (*result to o moc nevylepší)
    • int typ místo bool
    • pojmenování proměnných for cyklu
    • hledání v poli do samostatné funkce
      • první nápad na název nemusí být ten nejlepší
    • přidání do pole do samostatné funkce

    Příklad - odstraňování duplicit v poli (C)

    /* Pro účely úlohy předpokládejme, že "result" ukazuje
       na dostatečně dlouhé pole. */
    unsigned array_remove_duplicite_items(int array[], unsigned length,
            int result[]) {
        int i, j;
        unsigned result_length = 0;
    
        for (i = 0; i < length; i++) {
            int found = FALSE;
            for (j = 0; j < result_length; j++) {
                if (array[i] == result[j]) {
                    found = TRUE;
                    break;
                }
            }
    
            if (!found) {
                result[result_length] = array[i];
                result_length++;
            }
        }
    
        return result_length;
    }
    				

    Příklad - odstraňování duplicit v poli (C)

    /* Pro účely úlohy předpokládejme, že "result" ukazuje
       na dostatečně dlouhé pole. */
    unsigned array_remove_duplicite_items(int array[], unsigned length,
            int result[]) {
        unsigned result_length = 0;
    
        for (int i = 0; i < length; i++) {
            if (!array_find(result, result_length, array[i])) {
                result[result_length] = array[i];
                result_length++;
            }
        }
    
        return result_length;
    }
    				

    Příklad - odstraňování duplicit v poli (C)

    /* Pro účely úlohy předpokládejme, že "result" ukazuje
       na dostatečně dlouhé pole. */
    unsigned array_remove_duplicite_items(int array[], unsigned length,
            int result[]) {
        unsigned result_length = 0;
    
        for (int i = 0; i < length; i++) {
            if (!array_find(result, result_length, array[i])) {
                result[result_length] = array[i];
                result_length++;
            }
        }
    
        return result_length;
    }
    				

    Příklad - odstraňování duplicit v poli (C)

    /* Pro účely úlohy předpokládejme, že "result" ukazuje
       na dostatečně dlouhé pole. */
    unsigned array_remove_duplicite_items(int array[], unsigned length,
            int result[]) {
        unsigned result_length = 0;
    
        for (int i = 0; i < length; i++) {
            if (!array_find(result, result_length, array[i])) {
                result_length = array_append(result, result_length, array[i]);
            }
        }
    
        return result_length;
    }
    				

    Příklad - odstraňování duplicit v poli (C)

    /* Pro účely úlohy předpokládejme, že "result" ukazuje
       na dostatečně dlouhé pole. */
    unsigned array_remove_duplicite_items(int array[], unsigned length,
            int result[]) {
        unsigned result_length = 0;
    
        for (int i = 0; i < length; i++) {
            if (!array_find(result, result_length, array[i])) {
                result_length = array_append(result, result_length, array[i]);
            }
        }
    
        return result_length;
    }
    				

    Ještě nám vadí pojmenování funkce array_find. Vrací v podstatě boolean a radši bychom měli is v názvu.

    Příklad - odstraňování duplicit v poli (C)

    Řešení

    /* Pro účely úlohy předpokládejme, že "result" ukazuje
       na dostatečně dlouhé pole. */
    unsigned array_remove_duplicite_items(int array[], unsigned length,
            int result[]) {
        unsigned result_length = 0;
    
        for (int i = 0; i < length; i++) {
            if (!is_in_array(result, result_length, array[i])) {
                result_length = array_append(result, result_length, array[i]);
            }
        }
    
        return result_length;
    }
    				

    Příklad - Ctecka.cs

    Zadání

    Opravte v kódu chyby.

    Kontext

    Třída byla součástí většího programu, který implementoval Huffmanovo kódování. Vybraná třída načítá vstup ze souboru a připravuje ho pro další zpracování.

    Programování v pseudokódu

    Pseudokód, proces tvorby metod, návrh metod, psaní kódu metody

    PPP (Pseudocode Programming Process)

    Pseudokód - co to je?

    Neformální zápis funkce algoritmu, třídy, metody v téměř přirozeném jazyce.

    Zásady pro psaní pseudokódu

    Příklad pseudokódu metody

    Špatný pseudokód, obsahuje řadu implementačních detailů

    increment resource number by 1
    allocate a dlg struct using malloc
    if malloc() returns NULL then return 1
    invoke OSrsrc_init to initialize a resource for the operating system
    *hRsrcPtr = resource number
    return 0
    				

    Příklad pseudokódu metody

    Špatný pseudokód, obsahuje řadu implementačních detailů

    increment resource number by 1
    allocate a dlg struct using malloc
    if malloc() returns NULL then return 1
    invoke OSrsrc_init to initialize a resource for the operating system
    *hRsrcPtr = resource number
    return 0
    					

    Správný pseudokód, neomezuje se na implementaci v C

    Keep track of current number of resources in use
    If another resource is available
    	Allocated a dialog box structure
    	If a dialog box structure could be allocated
    	Note that one more resource is in use
    	Initialize the resource
    	Store the resource number at the location provided by the caller
    	Endif
    Endif
    Return TRUE if a new resource was created; otherwise return FALSE
    					

    Rady pro psaní

    Kontrola "prerekvizit"

    Rady pro psaní (pokr.)

    Definice problému

    Rady pro psaní (pokr.)

    Napsání

    Příklad - pseudokód

    Úloha je lehkým zjednodušením úlohy řešené v rámci kódu mé diplomové práce. Konkrétně jsem potřeboval implementovat bitové operace na "velkých číslech".

    Pokud se vám zdá ukládání bitů po jednom jako neefektivní, máte pravdu. Ale je to nejjednodušší řešení a optimalizovat se ho vyplatí až v případě, že nás opravdu někde začne "tlačit bota". Což v případě diplomové práce taky nemusí nastat vůbec :-)

    Pokud přemýšlíte, proč není číslo uloženo jako dvojkový doplněk rovnou, odpověď je inženýrská: PHP obsahuje knihovnu bcmath, která používá řetězcový zápis čísel a implementuje základní aritmetické operace (+, -, *, /, %,...). Je méně pracné implementovat převod mezi formáty a (triviálně) bitové operace, než (relativně obtížně) implementovat všechny operace.

    Pokud si z prvního ročníku nepamatujete, co je to dvojkový doplněk, pomůže wikipedie.

    Příklad - pseudokód (algoritmus řešení)

    1. Remember if the value is negative.
    2. If the value is negative, trim the sign (i.e. compute the absolute value).
    3. Compute the bits of the absolute value (using standard algorithm).
    4. If the first bit is non-zero, prefix the bits with zero bit.
    5. If the number was negative, invert the bits and add one.
    6. Return the bits.

    V tomto příkladu bude vše v angličtině, protože to tak mám i v diplomové práci.

    Příklad - pseudokód (tam ...)

    /** ... */
    function bignum_to_bits($bignum) {
        /* Remember if the value is negative. */
    
        /* If the value is negative, trim the sign (i.e. compute the
         absolute value). */
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
    
        /* If the number was negative, invert the bits and add one. */
    
        /* Return the bits. */
    }
    				

    Příklad - pseudokód (krok 2)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative. */
        $negative = ($bignum[0] == "-");
    
        /* If the value is negative, trim the sign (i.e. compute the
         absolute value). */
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
    
        /* If the number was negative, invert the bits and add one. */
    
        /* Return the bits. */
    }
    				

    Příklad - pseudokód (krok 3)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative. */
        $negative = ($bignum[0] == "-");
    
        /* If the value is negative, trim the sign (i.e. compute the
         absolute value). */
        if ($negative) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
    
        /* If the number was negative, invert the bits and add one. */
    
        /* Return the bits. */
    }
    				

    Příklad - pseudokód (krok 4)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
    
        /* If the number was negative, invert the bits and add one. */
    
        /* Return the bits. */
    }
    				

    Sloučení prvních dvou kroků.

    Příklad - pseudokód (krok 5)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = array();
        do {
            array_unshift($bits, (int)bcmod($bignum, 2));
            $bignum = bcdiv($bignum, 2);
        } while ($bignum != "0");
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
    
        /* If the number was negative, invert the bits and add one. */
    
        /* Return the bits. */
    }
    				

    Příklad - pseudokód (krok 6)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = array();
        do {
            array_unshift($bits, (int)bcmod($bignum, 2));
            $bignum = bcdiv($bignum, 2);
        } while ($bignum != "0");
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        if ($bits[0] != 0) {
            array_unshift($bits, 0);
        }
    
        /* If the number was negative, invert the bits and add one. */
    
        /* Return the bits. */
    }
    				

    Příklad - pseudokód (krok 7)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = array();
        do {
            array_unshift($bits, (int)bcmod($bignum, 2));
            $bignum = bcdiv($bignum, 2);
        } while ($bignum != "0");
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        if ($bits[0] != 0) {
            array_unshift($bits, 0);
        }
    
        /* If the number was negative, invert the bits and add one. */
        if ($negative) {
            for ($i = 0; $i < count($bits); $i++) {
                $bits[$i] = 1 - $bits[$i];
            }
    
            for ($i = count($bits) - 1; $i >= 0; $i--) {
                if ($bits[$i] == 0) {
                    $bits[$i] = 1;
                    break;
                } else {
                    $bits[$i] = 0;
                }
            }
        }
    
        /* Return the bits. */
    }
    				

    Příklad - pseudokód (krok 8)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = array();
        do {
            array_unshift($bits, (int)bcmod($bignum, 2));
            $bignum = bcdiv($bignum, 2);
        } while ($bignum != "0");
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        if ($bits[0] != 0) {
            array_unshift($bits, 0);
        }
    
        /* If the number was negative, invert the bits and add one. */
        if ($negative) {
            for ($i = 0; $i < count($bits); $i++) {
                $bits[$i] = 1 - $bits[$i];
            }
    
            for ($i = count($bits) - 1; $i >= 0; $i--) {
                if ($bits[$i] == 0) {
                    $bits[$i] = 1;
                    break;
                } else {
                    $bits[$i] = 0;
                }
            }
        }
    
        /* Return the bits. */
        return $bits;
    }
    				

    Příklad - pseudokód (hotovo?)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = array();
        do {
            array_unshift($bits, (int)bcmod($bignum, 2));
            $bignum = bcdiv($bignum, 2);
        } while ($bignum != "0");
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        if ($bits[0] != 0) {
            array_unshift($bits, 0);
        }
    
        /* If the number was negative, invert the bits and add one. */
        if ($negative) {
            for ($i = 0; $i < count($bits); $i++) {
                $bits[$i] = 1 - $bits[$i];
            }
    
            for ($i = count($bits) - 1; $i >= 0; $i--) {
                if ($bits[$i] == 0) {
                    $bits[$i] = 1;
                    break;
                } else {
                    $bits[$i] = 0;
                }
            }
        }
    
        /* Return the bits. */
        return $bits;
    }
    				

    V tuto chvíli vypadá funkce jako hotová. Ale zdaleka není...

    Příklad - pseudokód (refaktorizace)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = array();
        do {
            array_unshift($bits, (int)bcmod($bignum, 2));
            $bignum = bcdiv($bignum, 2);
        } while ($bignum != "0");
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        if ($bits[0] != 0) {
            array_unshift($bits, 0);
        }
    
        /* If the number was negative, invert the bits and add one. */
        if ($negative) {
            $bits = bits_add_one(bits_invert($bits));
        }
    
        /* Return the bits. */
        return $bits;
    }
    				

    Příklad - pseudokód (refaktorizace II)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = positive_bignum_to_bits($bignum);
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        if ($bits[0] != 0) {
            array_unshift($bits, 0);
        }
    
        /* If the number was negative, invert the bits and add one. */
        if ($negative) {
            $bits = bits_add_one(bits_invert($bits));
        }
    
        /* Return the bits. */
        return $bits;
    }
    				

    Příklad - pseudokód (refaktorizace III)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = ($bignum[0] == "-")) {
            $bignum = substr($bignum, 1);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = positive_bignum_to_bits($bignum);
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        $bits = bits_prefix_with_zero_if_needed($bits);
    
        /* If the number was negative, invert the bits and add one. */
        if ($negative) {
            $bits = bits_add_one(bits_invert($bits));
        }
    
        /* Return the bits. */
        return $bits;
    }
    				

    Příklad - pseudokód (refaktorizace IV)

    function bignum_to_bits($bignum) {
        /* Remember if the value is negative, trim the sign if so. */
        if ($negative = bignum_is_negative($bignum)) {
            $bignum = bignum_abs($bignum);
        }
    
        /* Compute the bits of the absolute value (using standard
         algorithm). */
        $bits = positive_bignum_to_bits($bignum);
    
        /* If the first bit is non-zero, prefix the number with
         zero bit. */
        $bits = bits_prefix_with_zero_if_needed($bits);
    
        /* If the number was negative, invert the bits and add one. */
        if ($negative) {
            $bits = bits_add_one(bits_invert($bits));
        }
    
        /* Return the bits. */
        return $bits;
    }
    				

    Příklad - pseudokód (hotovo!)

    function bignum_to_bits($bignum) {
        if ($negative = bignum_is_negative($bignum)) {
            $bignum = bignum_abs($bignum);
        }
    
        $bits = positive_bignum_to_bits($bignum);
    
    
        $bits = bits_prefix_with_zero_if_needed($bits);
    
    
        if ($negative) {
            $bits = bits_add_one(bits_invert($bits));
        }
    
        return $bits;
    }
    				

    Odstranění víceméně zbytečných komentářů

    Příklad - pseudokód (... a zpět)

    function bignum_to_bits($bignum) {
        if ($negative = bignum_is_negative($bignum)) {
            $bignum = bignum_abs($bignum);
        }
    
        $bits = positive_bignum_to_bits($bignum);
    
    
        $bits = bits_prefix_with_zero_if_needed($bits);
    
    
        if ($negative) {
            $bits = bits_add_one(bits_invert($bits));
        }
    
        return $bits;
    }
    								
    
    Remember if the value is negative.
    If the value is negative, trim the sign
        (i.e. compute the absolute value).
    
    Compute the bits of the absolute value
        (using standard algorithm).
    
    If the first bit is non-zero,
        prefix the bits with zero bit.
    
    If the number was negative,
        invert the bits and add one.
    
    
    Return the bits.
    
    								

    Proces tvorby metody: shrnutí

    Kódování metody

    Kontrola kódu

    Konečný úklid

    Příklad - procedura na hlášení chyb

    Napište pseudokód

    Výchozí požadavek

    Potřebuji proceduru, která vypíše chybovou hlášku odpovídající zadanému chybovému kódu.

    Neformální specifikace

    ReportErrorMessage() takes an error code as an input argument and outputs an error message corresponding to the code.

    It's responsible for handling invalid codes.

    If the program is operating interactively, the procedure displays the error message to the user.

    If it's operating in command line mode, the procedure logs the message to a message file.

    After outputting the message, the procedure returns a status value indicating whether it succeeded or failed.

    Postup psaní - "check-list"

    Požadavky jsou (snad) jasné a funkce se jeví jako logicky ucelená a "rozumná".

    High-level návrh

    Návrh pseudokódu

    Řešení

    set the default status to "fail"
    look up the message based on the error code
    
    if the error code is valid
    
        if doing interactive processing, display the error message
        interactively and declare success
    
        if doing command line processing, log the error message to
        the message file and declare success
    
    if the error code isn't valid, notify the user that an internal
    error has been detected
    
    return status information
    				

    Příklad - kopírování souboru

    Zadání

    Napište v jazyce C funkci, která zkopíruje soubor na jiné místo. Funkce vrátí nenulovou hodnotu, pokud se zkopírování povedlo, jinak vrátí nulu.

    Funkce nejdříve popište v pseudokódu, teprve pak napište implementaci v C.

    • možná se strhne diskuze nad použitím goto
    • zde nahrazuje v podstatě výjimky
    • je konzistentně použito, kód je přehlednější
      • buď by tam byl ten kód, tj. duplikování
      • nebo by se tam volala nějaká funkce

    Kopírování souboru - dostupné API (stdio.h)

    Kopírování souboru - pseudokód

    Řešení

    1. otevři vstupní soubor
    2. otevři výstupní soubor
    3. dokud neskončí vstupní soubor
      1. načti blok ze vstupního souboru
      2. zapiš blok do výstupního souboru
    4. zavři oba soubory

    Kopírování souboru - pseudokód

    Řešení (detailnější)

    1. otevři vstupní soubor
      • nejde? vrať chybu
    2. otevři výstupní soubor
      • nejde? zavři vstupní soubor, vrať chybu
    3. dokud neskončí vstupní soubor
      1. načti blok ze vstupního souboru
        • chyba? zavři oba soubory, vrať chybu
      2. zapiš blok do výstupního souboru
        • chyba? zavři oba soubory, vrať chybu
    4. zavři oba soubory
      • chyba? zavři i ten druhý, vrať chybu
      • (alespoň se pokusíme zavřít oba)
    5. "vrať" úspěch

    Kopírování souboru - implementace

    Možné řešení

    #define FALSE 0
    #define TRUE  (!FALSE)
    #define BUFFER_SIZE 4096
    
    int copy_file(const char *src_filename, const char *dest_filename) {
        FILE *src_file = NULL;     FILE *dest_file = NULL;
    
        if (src_filename == NULL) { return FALSE; }
        if (dest_filename == NULL) { return FALSE; }
    
        src_file = fopen(src_filename, "r");
        if (src_file == NULL) { goto error_no_cleanup; }
    
        dest_file = fopen(dest_filename, "w");
        if (dest_file == NULL) { goto error_cleanup_src; }
    
        while (!feof(src_file)) {
            char buffer[BUFFER_SIZE];
            size_t bytes_read, bytes_written;
    
            bytes_read = fread(buffer, sizeof(char), BUFFER_SIZE, src_file);
            if (bytes_read == 0) { goto error_cleanup_both; }
    
            bytes_written = fwrite(buffer, sizeof(char), bytes_read, dest_file);
            if (bytes_written < bytes_read) { goto error_cleanup_both; }
        }
    
        if (fclose(dest_file)) { goto error_cleanup_src; }
        if (fclose(src_file)) { goto error_no_cleanup; }
    
        return TRUE;
    
    error_cleanup_both:
        fclose(dest_file);
    error_cleanup_src:
        fclose(src_file);
    error_no_cleanup:
        return FALSE;
    }
    				

    GoTo

    Další příklady

    Příklad - layout HTML stránky

    Opravte chyby

    <body>
        <div id="nice-logo"><!-- Site logo --></div>
        <div id="leftbar"><!-- Navigation --></div>
        <div id="content">
            <!-- Actual content -->
            <h2 class="big-blue">Comments</h2>
            <!-- Readers' comments -->
        </div>
        <div id="footer"><!-- Copyright, ... --></div>
    </body>
    				
    • další varianta na pojmenování proměnných
    • (nejen) v HTML není dobré pojmenovávat podle vzhledu, ale podle účelu

    Příklad - layout HTML stránky

    <body>
        <div id="nice-logo"><!-- Site logo --></div>
        <div id="leftbar"><!-- Navigation --></div>
        <div id="content">
            <!-- Actual content -->
            <h2 class="big-blue">Comments</h2>
            <!-- Readers' comments -->
        </div>
        <div id="footer"><!-- Copyright, ... --></div>
    </body>
    				

    Příklad - layout HTML stránky

    Řešení

    <body>
        <div id="top-logo"><!-- Site logo --></div>
        <div id="navigation"><!-- Navigation --></div>
        <div id="content">
            <!-- Actual content -->
            <h2 class="comments">Comments</h2>
            <!-- Readers' comments -->
        </div>
        <div id="footer"><!-- Copyright, ... --></div>
    </body>
    				

    Příklad - je bod uvnitř obdélníka? (Java)

    Opravte chyby

    bool pointInRect(int x, int y,
            int x1, int y1, int x2, int y2) {
    
        return x >= x1 && x <= x2 && y >= y1 && y <= y2;
    }
    				
    • špatné pojmenování parametrů
      • implicitně se předpokládá, že levý horní roh (při "počtačových" souřadnicích) má číslo 1 a pravý dolní roh číslo 2 - to ale není samozřejmé
    • převedeme na popisnější
    • všimněte si formátování parametrů, co se nevešly na 1 řádku

    Příklad - je bod uvnitř obdélníka? (Java)

    bool pointInRect(int x, int y,
            int x1, int y1, int x2, int y2) {
    
        return x >= x1 && x <= x2 && y >= y1 && y <= y2;
    }
    				

    Příklad - je bod uvnitř obdélníka? (Java)

    Řešení (minimální)

    bool pointInRect(int x, int y,
            int left, int top,
            int right, int bottom) {
        return x >= left && x <= right
            && y >= top && y <= bottom;
    }
    				
    • parametrů je ale stále příliš mnoho
    • zavedeme vyšší abstrakci
    • diskuze nad pojmenováním (isPointInRectangle, pointIsInRect...)

    Příklad - je bod uvnitř obdélníka? (Java)

    Řešení

    bool pointInRect(Point point, Rect rect) {
        return point.getX() >= rect.getLeft()
            && point.getX() <= rect.getRight()
            && point.getY() >= rect.getTop()
            && point.getY() <= rect.getBottom();
    }
    				

    Příklad - pojídač komentářů (C)

    Opravte chyby

    if (*curr_pos == '/') {
        curr_pos++;
        if (*curr_pos == '/') {
            curr_pos++;
            while (*curr_pos && !is_newline(*curr_pos)) {
                curr_pos++;
            }
        } else {
            return NULL;
        }
    } else {
        return NULL;
    }
    				
    • chyby/speciální stavy ošetřit na začátku

    Příklad - pojídač komentářů (C)

    if (*curr_pos == '/') {
        curr_pos++;
        if (*curr_pos == '/') {
            curr_pos++;
            while (*curr_pos && !is_newline(*curr_pos)) {
                curr_pos++;
            }
        } else {
            return NULL;
        }
    } else {
        return NULL;
    }
    				

    Příklad - pojídač komentářů (C)

    Řešení

    if (*curr_pos != '/') {
        return NULL;
    }
    curr_pos++;
    
    if (*curr_pos != '/') {
        return NULL;
    }
    curr_pos++;
    
    while (*curr_pos && !is_newline(*curr_pos)) {
        curr_pos++;
    }
    				

    Příklad - array.h

    Zadání

    Opravte rozhraní C++ třídy zapouzdřující práci s polem.

    Příklad - hledání souboru

    Zadání

    Napište pseudokód funkce, která hledá soubor daného jména v určených adresářích. Hledání navíc dokáže rozpoznat archivy a hledat soubor i v nich.

    Vstupem funkce je seznam míst, kde hledat a jaký soubor, výstupem pak seznam umístění.

    • naschvál ve specifikaci chybí, co při chybách
      • kolik lidí si toho všimne?

    Příklad - hledání souboru (pseudokód)

    Řešení

    1. inicializuj seznam s výsledky jako prázdný
    2. pro každou cestu v seznamu míst
      1. jestliže jméno končí na .zip, .tar.gz apod.
        1. otevři archiv
        2. projdi všechny soubory v archivu
          1. jestli název souboru odpovídá hledanému, přidej ho do seznamu
        3. zavři archiv
      2. jinak předpokládej, že jde o název adresáře
        1. pro každý soubor v adresáři
          1. je-li to adresář, zanoř se
          2. jinak porovnej název s hledaným a při shodě ho přidej do seznamu
      3. pokud nastala chyba, ???
    3. vrať seznam s výsledky