Göm menyn

TDDD86 Datastrukturer, algoritmer och programmeringsparadigm

Stilguide


Stilguide

Följande är några stilkrav vi förväntar oss att dina program ska uppfylla för att de ska bli godkända. Detta är inte en fullständig lista; vänligen se respektive labb för andra specifika krav att uppfylla.


1: Whitespace och indentering
  • 1-1: Indentera: Öka indenteringen ett steg för varje måsvinge { och minska den ett steg efter varje avslutande måsvinge }.
  • Placera en radbrytning efter varje {.
  • Placera inte mer än ett uttryck på samma rad.
    // dåligt
    int x = 3;  int y = 4;  x++;
    if (a == b) { foo(); }
    
  • 1-2: Långa rader: Bryt långa rader genom att trycka Enter efter en operator och fortsätta på nästa rad. Indentera den andra delen av raden två steg. (Qt Creator sköter detta atuomatiskt.) Till exempel:
    int result = reallyLongFunctionOne() + reallyLongFunctionTwo() + 
            reallyLongFunctionThree() + reallyLongFunctionFour();
    
    int result2 = reallyLongFunction(parameterOne, parameterTwo, parameterThree,
            parameterFour, parameterFive, parameterSix);
    
  • 1-3: Uttryck: Placeera ett mellanslag mellan operatorer och deras operander.

    int x = (a + b) * c / d + foo();
    
  • 1-4: Tomrader: Placera en tom rad mellan funktioner och grupper av satser. Placera inte ut tomrader i onödan, e.g., inuti logiskt sammanhängande stycken av kod. Det ska inte bli luftigt, det ska bli lättläst.

    void foo()
    {
        ...
    }
                              // tom rad här
    void bar()
    {
        ...
    }
    
2: Namngivning och variabler
  • 2-1: Namn: Ge variabler deskriptiva namn, som firstName eller homeworkScore. Undvik enbokstavsnamn som x eller c om de inte har en betydelse i sammanhanget (x-koordinaten till exempel). Ett vanligt undantag här är loopräknare som i, j, k och gränser som m, n. En bra tumregel för namngivning av funktioner är att fundera på hur det ser ut i kod som ska anropa den.

  • 2-2: Versaler: Namnge variabler och funktioner med kamelnotation likeThis, namnge klasser med Pascalnotation LikeThis och namnge konstanter med versaler LIKE_THIS.

  • 2-3: Definitionsområde (scope): Deklarera variabler i så snävt definitionsområde som möjligt. Om till exempel en variabel endast används inuti en specifik if-sats definitionsområde, deklarera den då hellre inuti if-satsen än i början av funktionen eller filen.

  • 2-4: Typer: Välj ändamålsenliga datatyper för dina variabler. Om en given variabel enbart kommer att lagra heltal, ge den då typen int snarare än double.
  • 2-5: Föredra C++-strängar framför C-strängar: C++ har, förvirrande nog, två sorters strängar: string-klassen från C++ och den äldre char* (array/fält av characters) från C. Så långt det är möjligt bör du använda string-typen från C++ snarare än den äldre C-strängstypen.
    // dåligt: sträng i C-stil
    char* str = "Hello there";
    
    // bra: sträng i C++-stil
    string str = "Hello there";
    
  • 2-6: Konstanter: Om ett specifikt konstant värde används ofta i din kod, deklarera den som en konstant med const eller ett konstantuttryck med constexpr och referera hela tiden till den konstanten i resten av din kod istället för att referera till det motsvarande värdet.

    constexpr int MEANING_OF_LIFE = 42;
    
  • 2-7: Undvik globala variabler: Deklarera aldrig en global variabel. De enda globalt deklararerade namnen i din kod bör vara const-deklarerade konstanter. Istället för att göra ett värde globalt, överför det som en paramter och/eller returnera det vid behov.

    // dåligt
    int count;  // global variabel; dåligt!
    
    void func1() {
        count = 42;
    }
    
    void func2() {
        count++;
    }
    
    int main() {
        func1();
        func2();
    }
    
3: Grundläggande programstruktur
  • 3-1: for vs while: Använd en for-loop när antalet repetitioner är känt; använd en while-loop när antalet repetitioner är okänt. Föredra range-based iteration framför index om målet är något i stil med "gå igenom varje element".

    // upprepa exakt size/2 gånger
    for (int i = 0; i < size / 2; i++) {
        ...
    }
    
    // gå igenom varje User i vektorn
    for (User& user : set<User> loggedInUsers)) {
        ...
    }
    
    // upprepa till det inte finns flera rader
    while (...) {
        ...
    }
    
  • 3-2: break och continue: I allmänhet bör du undvika att använda break- eller continue-satser i loopar om inte absolut nödvändigt.

  • 3-3: if/else-mönster: Vid användning av if/else-satser, välj omsorgsfullt mellan de olika if- och else-mönstren beroende på hur villkoren är relaterade till varandra. Undvik redundanta eller onödiga if-tester.

    // dåligt
    if (grade >= 90) {
        cout << "You got an A!";
    }
    if (grade >= 80 && grade < 90) {
        cout << "You got a B!";
    }
    if (grade >= 70 && grade < 80) {
        cout << "You got a C!";
    }
    ...
    
    // bra
    if (grade >= 90) {
        cout << "You got an A!";
    } else if (grade >= 80) {
        cout << "You got a B!";
    } else if (grade >= 70) {
        cout << "You got a C!";
    }
    ...
    
  • 3-4: Boolesk zen 1: Om du har en if/else-sats som returnerar ett värde av typ bool baserat på ett test, returnera bara resultatet av testet direkt istället.

    // dåligt
    if (score1 == score2) {
        return true;
    } else {
        return false;
    }
    
    // bra
    return score1 == score2;
    
  • 3-5: Boolesk zen 2: Testa aldrig om ett värde av typ bool är == eller != literalerna true eller false.

    // dåligt
    if (x == true) {
        ...
    } else if (x != true) {
        ...
    }
    
    // bra
    if (x) {
        ...
    } else {
        ...
    }
    
4: Redundans
  • 4-1: Minimera redundant kod: Om du upprepar samma kod fler än två gånger, hitta ett sätt att ta bort den redundanta koden så att den endast uppträder en gång. Placera den till exempel i enhjälpfunktion som anropas från båda platserna. Om den upprepade koden är nästan men inte helt lik kan det gå att låta hjälpfunktionen ta en parameter för att representera det som skiljer.

    // dåligt
    foo();
    x = 10;
    y++;
    ...
    
    foo();
    x = 15;
    y++;
    
    // bra
    helper(10);
    helper(15);
    ...
    
    void helper(int newX) {
        foo();
        x = newX;
        y++;
    }
    
  • 4-2: if/else-faktorering: Flytta ut gemensam kod från if/else-satser så att den inte upprepas.
    // dåligt
    if (x < y) {
        foo();
        x++;
        cout << "hi";
    } else {
        foo();
        y++;
        cout << "hi";
    }
    
    // bra
    foo();
    if (x < y) {
        x++;
    } else {
        y++;
    }
    cout << "hi";
    
  • 4-3: Strukturering av funktioner: Om du har en enskild funktion som är väldigt lång, dela upp den i mindre underfunktioner. Definitionen av "väldigt lång": låt oss säga att 40-50 rader är på gränsen. Om du försöker beskriva syftet med en funktion och märker att du säger "och" väldigt ofta betyder det antagligen att funktionen gör för många saker och borde delas upp i underfunktioner.

5: Effektivitet
  • 5-1: Spara resultat av dyra funktionsanrop i en variabel: Om du anropar en dyr funktion och använder resultatet många gånger, spara det resultatet i en variabel snarare än att anropa funktionen flera gånger.
    // dåligt
    if (reallySlowSearchForIndex("abc") >= 0) {
        remove(reallySlowSearchForIndex("abc"));
    }
    
    // bra
    int index = reallySlowSearchForIndex("abc");
    if (index >= 0) {
        remove(index);
    }
    
6: Kommentarer
  • 6-1: Filhuvud: Placera en deskriptiv kommentar längst upp i varje fil som beskriver filens syfte. Antag att läsaren av dina kommentarer är en intelligent programmerare som inte sett labben förut. Ditt kommentarshuvud bör inkludera åtminstone ditt namn, liuid och en kort beskrivning av uppgiften. Om uppgiften omfattar inskickning av flera filer bör respektive fils kommentarhuvud beskriva den filen/klassen och dess huvudsyfte i programmet.

  • 6-2: Citera källor: Era labbar ska vara ert arbete i labbparet. Om du tagit del av några externa resurser som hjälper dig att skapa ditt program (en bok, föreläsningbilder från andra kurser, webbsidor, råd från andra personer, etc.) bör du lista dem i en kommentar i början av filen. Att inte lista sådant kan bedömas som vilseledning, och rapporteras till disciplinnämnden.

  • 6-3: Funktionshuvud: Placera ett kommentarshuvud på varje funktion i din fil. Kommentaren bör beskriva vad funktionen gör.

  • 6-4: Parametrar/returvärden: Om din funktion tar emot parametrar, beskriv kortfattat deras syfte och mening. Om din funktion returnerar ett värde, beskriv kortfattat vad den returnerar.

  • 6-5: För-villkor/antaganden: Om din funktion gör några antaganden, som att parametrar har vissa värden, nämn detta i dina kommentarer.

  • 6-6: Undantag: Om din funktion uttryckligen kastar undantag för olika förväntade klasser av fel, nämn detta i dina kommentarer. Var specifik med vilken typ av undantag du kastar och under vilka omständigheter det sker. (Till exempel, "Throws an IllegalArgumentException if the liuid passed is invalid.")

  • 6-7: Taktiska kommentarer: Inuti dina funktioner, om du har kodavsnitt som är långa, komplexa eller icke-triviala, placera väl valda kommentarer nära dessa rader som beksriver vad de gör.

  • 6-8: Implementationsdetaljer: Kommentarer i funktions-, klass eller fil-huvuden bör beskriva dessas syfte och beteende. Däremot ska de inte beskriva i detalj hur de är implementerade. Beskriv koden på en hög nivå. Att skriva "summera alla poäng" är mer beskrivande och lärorikt än "vi använder en for-loop för att gå igenom och lägga till alla värden till akumulatorn".

  • 6-9: TODO: Ta bort alla // TODO:-kommentarer innan du lämnar in ett program.

  • 6-10: Bortkommenterad kod: Det betraktas som dålig stil att lämna in kod där stycken av koden är "bortkommenterad". Det går bra att kommentera bort kod under arbetets gång, men om programmet är färdigt och sådan kod inte behövs, ta helt enkelt bort den.

7: Funktioner och procedurell design
  • 7-1: Design av en bra funktion: En väldesignad funktion har egenskaper som:

    • Genomför fullständigt en välavgränsad uppgift.
    • Gör inte för stor arbete.
    • Är inte onödigt sammankopplad med andra funktioner.
    • Lagrar data i så snävt definitionsområde som möjligt.
    • Påvisar och underdelar strukturen i det övergripande progammet.
    • Hjälper till att ta bort redundans som annars skulle finnas i det övergripande programmet.
  • 7-2: Värde- vs. referensparametrar: Använd referensparametrar för att skicka 'ut' information från en funktion, eller när funktionen kan behöva ändra på värdet på paramtern som förs in, eller när funktionen behöver returnera flera värden. Använd inte referensparametrar när det inte är nödvändigt eller inte medför några fördelar. Notera att a, b och c inte är referensparametrar till följande funktion eftersom de inte behöver vara det.

    /* 
     * Solves a quadratic equation ax^2 + bx + c = 0,
     * storing the results in output parameters root1 and root2.
     * Assumes that the given equation has two real roots.
     */
    void quadratic(double a, double b, double c,
                   double& root1, double& root2) {
        double d = sqrt(b * b - 4 * a * c);
        root1 = (-b + d) / (2 * a);
        root2 = (-b - d) / (2 * a);
    }
    
  • 7-3: 'Ut'-referensparameter vs. return: När ett enda värde behöver skickas tillbaka från en funktion och kan tillhandahållas genom en 'ut'-referens eller ett returvärde, föredra ett returvärde.

    // dåligt
    void max(int a, int b, int& result) {
        if (a > b) {
            result = a;
        } else {
            result = b;
        }
    }
    
    // bra
    int max(int a, int b) {
        if (a > b) {
            return a;
        } else {
            return b;
        }
    }
    
  • 7-4: Överför objekt genom referens: När du skickar ett objekt som parameter till en funktion bör du normalt överföra den via referens eftersom hela objektet måste kopieras som det överförs som värde. Kopiering av objekt är dyrt.
    // dåligt
    void process(BankAccount account) {
        ...
    }
    
    // bra
    void process(BankAccount& account) {
        ...
    }
    
  • 7-5: Referens vs. pekare: Om du känner till pekare i C/C++ från tidigare programmeringserfarenhet, föredra att överföra parametrar som referenser över pekare så mycket som möjligt. Ett skäl till detta är att referenser, olikt pekare, inte kan anta värdet nullptr.

    // dåligt
    // accepts a pointer to an account
    void process(BankAccount* account) {
        ...
    }
    
    // bra
    // accepts a reference to an account
    void process(BankAccount& account) {
        ...
    }
    
  • 7-6: referens till const som parameter: Om du överför ett objekt till en funktion och din kod inte kommer att ändra på tillståndet i objektet, överför det som en referens till const.
    // dåligt
    // accepts a reference to an account
    void display(BankAccount& account) {
        ...
    }
    
    // bra
    // accepts a const reference to an account
    void display(const BankAccount& account) {
        ...
    }
    
  • 7-7: Undvik kedjning, där flera funktioner anropar varandra i en kedja utan att återvända till main. Säkerställ att main är en koncis sammanfattning av ditt övergripande program. Här är ett översiktligt diagram över ett anropsflöde med (vänster) och utan (höger) kedjning:
    // dåligt
    main
    |
    +-- function1
        |
        +-- function2
            |
            +-- function3
                |
                +-- function4
                |
                +-- function5
                    |
                    +-- function6
    
    // bra
    main
    |
    +-- function1
    |
    +-- function2
    |   |
    |   +-- function3
    |
    +-- function4
    |   |
    |   +-- function5
    |
    +-- function6
    
8: Klassdesign
  • 8-1: Inkapsling: Kapsla in dina objekt genom att deklarera datafälten i din klass private.

    class Student {
    private:
        int homeworkScore;
            ...
    
  • 8-2: .h vs. .cpp: Placera alltid deklarationen av en klass och dess medlemmar i sin egen fil, ClassName.h. Placera definitionen av dessa medlemmar i en egen fil, ClassName.cpp. Skydda alltid .h-filens klassdeklaration med ett preprocessorblock #ifndef/define/endif för att undvika multipla deklarationer av sama klass.
    // Point.h
    #ifndef POINT_H
    #define POINT_H
    class Point {
    public:
        Point(int x, int y);
        int getX() const;
        int getY() const;
        string toString() const;
        void translate(int dx, int dy);
    
    private:
        int m_x;
        int m_y;
    };
    #endif // END POINT_H
    
    // Point.cpp
    #include "Point.h"
    
    Point::Point(int x, int y) {
        m_x = x;
        m_y = y;
    }
    
    void Point::translate(int dx, int dy) {
        m_x += dx;
        m_y += dy;
    }
    ...
    
  • 8-3: class vs. struct: Använd alltid class förutom om du skapar en väldigt liten och enkel datatyp som enbart behöver några få publika medlemsvariabler och kanske en konstruktor för att initialisera dem- Exempel på sådana små struct-typer kan vara en Point eller en LinkedListNode.
  • 8-4: Undvik onödiga fält; använd fält för att lagra data som är viktigt för ditt objekt men inte för att lägra temporära värden som bara används inuti ett enskilt anrop till en funktion.
  • 8-5: Hjälpfunktioner: Om du lägger till en medlemsfunktion till din klass som inte är en del av en uppgiftslydelse, deklarera den private så att utomstående kod inte kan anropa den.

    class Student {
        ...
    private:
        double computeTuitionHelper();
    
  • 8-6: const-medlemmar: Om en given medlemsfunktion inte ändrar det anropade objektets tillstånd, deklarera den const.
    class Student {
    public:
        int getID() const;
        double getGPA(int year) const;
        void payTuition(Course& course);
        string toString() const;
        ...
    

Sidansvarig: Ahmed Rezine
Senast uppdaterad: 2022-08-23