Обработване на грешки

29.11.2016

В този епизод:

Error handling

Имало едно време чисто С

Пример в C

#include <stdio.h>
#include <errno.h>
#include <string.h>

extern int errno;

int main ()
{
    FILE* pf = fopen("unexist.txt", "rb");
    if (pf == NULL) {
        fprintf(stderr, "Value of errno: %d\n", errno);
        perror("Error printed by perror");
        fprintf(stderr, "Error opening file: %s\n", strerror(errno));
    }
    else {
        fclose(pf);
    }
    return 0;
}

Имало едно време един език Go

Има грубо-казано 2 начина

Връщане на грешка

type error interface {
    Error() string
}
type PathError struct {
    Op string    // "open", "unlink", etc.
    Path string  // Файлът с грешката
    Err error    // Грешката, върната от system call-a
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

Стандартна употреба

func ReadFile(filename string) ([]byte, error) {
    f, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    //...
}

или малко по-сложно:

func CreateFile(filename string) (*os.File, error) {
    var file, err = os.Create(filename)
    if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
        deleteTempFiles() // Free some space
        return os.Create(filename)
    }
    return file, err
}

Errors are values

Често оплакване на Go програмисти е количеството проверки за грешки:

if err != nil {
    return err
}

Пример

if _, err := fd.Write(p0[a:b]); err != nil {
    return err
}
if _, err := fd.Write(p1[c:d]); err != nil {
    return err
}
if _, err := fd.Write(p2[e:f]); err != nil {
    return err
}

Може да стане:

var err error
write := func(buf []byte) {
    if err == nil {
        _, err = w.Write(buf)
    }
}
write(p0[a:b])
write(p1[c:d])
write(p2[e:f])
if err != nil {
    return err
}

Създаване на грешки

func someFunc(a int) (someResult, error) {
    if a <= 0 {
        return nil, errors.New("a must be positive!")
    }
    // ...
}
func someFunc(a int) (someResult, error) {
    if a <= 0 {
        return nil, fmt.Errorf("a is %d, but it must be positive!", a)
    }
    // ...
}

Припомняне на defer

package main

import "fmt"

func main() {
    fmt.Println("start")
    defer fmt.Println("first")

    if false { // ex. try to open a file
        fmt.Println("error")
        return
    }
    defer fmt.Println("second")

    fmt.Println("done")
}

Паника!

Уточнения

Избягвайте ненужното изпадане в паника

recover

Example

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}
func g(i int) {
    if i > 2 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}
package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()
	fmt.Println("Calling g.")
	g(0)
	fmt.Println("Returned normally from g.")
}
func g(i int) {
	if i > 2 {
		fmt.Println("Panicking!")
		panic(fmt.Sprintf("%v", i))
	}
	defer fmt.Println("Defer in g", i)
	fmt.Println("Printing in g", i)
	g(i + 1)
}

//END OMIT

Тестове и документация

Disclamer

Днес няма да си говорим за acceptance testing, quality assurance или нещо, което се прави от "по-низшия" отдел във фирмата.

Всичко тук е дело на програмиста.

Митът

Проектът идва с готово, подробно задание.

Прави се дизайн.

С него работата се разбива на малки задачи.

Те се извършват последователно.

За всяка от тях пишете кода и приключвате.

Изискванията не се променят, нито се добавя нова функционалност.

Митът v2.0

Щом съм написал един код, значи ми остава единствено да го разцъкам - няколко print-а, малко пробване в main функцията и толкова.

Така или иначе няма да се променя.

А ако (не дай си боже) това се случи - аз съм го писал, знам го, няма как да допусна грешка.

Най-много да го поразцъкам още малко.

Тежката действителност

Заданията винаги се променят.

Често се налага един код да се преработва.

Писането на код е сложна задача - допускат се грешки.

Програмистите са хора - допускат грешки.

Промяната на модул в единия край на системата като нищо може да счупи модул в другия край на системата.

Идва по-добра идея за реализация на кода, по ред причини.

Искаме да автоматизираме нещата

За всичко съмнително ще пишем сценарий, който да "цъка".

Всеки сценарий ще изпълнява кода и ще прави няколко твърдения за резултатите.

Сценариите ще бъдат обединени в групи.

Пускате всички тестове с едно бутонче.

Резултатът е "Всичко мина успешно" или "Твърдения X, Y и Z в сценарии A, B и C се оказаха неверни".

Искаме да тестваме и производителността на нашия код.

Видове тестове

За какво ни помагат тестовете

За какво не служат тестовете

testing

Разбрахме се, че тестовете са ни супер важни.

Очевидно в стандартната библиотека на Go, има пакет за това.

За да тестваме foo.go, създаваме foo_test.go в същата директория, който тества foo.go

Ако тестваме пакета foo можем:

С go test ./... пускаме тестовете на един цял проект :)

Тестовете в testing

func TestFibonacciFastest(t *testing.T) {
    n := FibonacciFastest(0)
    if n != 1 {
        t.Error("FibonnaciFastest(0) returned" + n + ", we expected 1")
    }
}

Benchmark тестове

func BenchmarkFibonacciFastest(b *testing.B) {
    for i := 0; i < b.N; i++ {
        FibonacciFastest(40)
    }
}

Demo

Документиране на кода

go генерира автоматична документация на нашия код, вземайки под внимание:

/*
    Example fobonacci numbers implementation
*/
package fibonacci
// Fastest Fibonacci Implementation
func FibonacciFastest(n uint64) uint64 {
// lookupTable stores computed results from FibonacciFast or FibonacciFastest.
var lookupTable = map[uint64]uint64{}

Виждане на документацията

На всички локално инсталирани пакети

godoc -http=:6060

Документация на (почти) всички go пакети качени в BitBucket, GitHub, Launchpad и Google Project Hosting

Example тестове - шантавата част

Foo -> ExampleFoo
func ExampleHello() {
    Hello("hello")
    // Output:
    // hello
}

Въпроси?