Typový system versus unittesty

Re:Typový system versus unittesty
« Odpověď #930 kdy: 26. 10. 2018, 11:09:29 »
Jen pro doplnění. Kdysi jsem četl jeden zajímavý článek (bohužel už ho teď nedohledám), kde autor považoval za velice důležitý parametr jazyka konzistentní hustotu informací (to je moje parafráze, jak to nazval on už si nepamatuji).  Šlo o to, aby nebylo na 3 řádcích "prázdno" a pak na jednom řádku složitá konstrukce. Tímhle nešvarem dle mého názoru trpí třeba Perl nebo Python, naopak jazyky jako Lua nebo Go jsou skoro až řídké (alespoň dle mých zkušeností). Nejzajímavější na tom je to, že ve výsledku nevidím, že by program v Go  nebo Lue byl delší než odpovídající program v v Perlu nebo Pythonu, skoro naopak.

Tahle ta konzistentní hustota je podle autora článku klíčová pro snadnou čitelnost/udržitelnost kódu.

Mel bys priklad? Zajimal by me ten nesvar u perlu nebo pythonu... jak to vypada.


Petr

Re:Typový system versus unittesty
« Odpověď #931 kdy: 28. 10. 2018, 14:25:53 »
Souhlasim. Neslo mi o jazyky s dynamickou syntaxi, o jejichz realne prakticnosti mam pochyby,  ale uvidime, co prinese budoucnost. Slo mi o fenomen slozitosti, ktery se tu prehlizi a je imho vyznamne podstatnejsi ohledne kvality a spolehlivosti vysledne aplikace. Chci-li vysokou spolehlivost, nevyresim to typovym systemem, ale a) vhodnou metodou vyvoje software, zalozenou na dukladnych testech, viz treba extremni programovani, b) kvalitnim testovacim prostredim a c) zvolenám prostredku, ktere mi rozumne snizi druhotnou slozitost, jenz je vyznamnym zdrojem chyb (proto typicky neprogramujeme ve strojovem kodu nebo asm a proto se vyvoj nezastavil ani u jejich nastupcu C, C++, Java /C# nebo dnes popularni JS a Python, kde Python je nejpokrocilejsi, ale take nejmene vykonny). Ony ty testy take zvysuji druhotnou slozitost, ale maji vyhodu, ze nejsou soucasti produkcni aplikace a nezasahuji do ni, ale to uz se ale opakuji. Typovy system slozitost programu zvysuje a to primo umerne se svou silou. Tedy cim dukladneji resi jeden zdroj chyb, tim posiluje jiny zdroj chyb, druhotnou slozitost. Argument kvality a spolehlivosti je u staticky typu proto falesny. Jejich opodstatneni je jine, v moznosti lepsi optimalizace a vyssiho vykonu aplikace. Ale k tomu neni potreba, hadam, vyssi typovy system, staci k tomu datove typy na urovni architektury.
Jen pro doplnění. Kdysi jsem četl jeden zajímavý článek (bohužel už ho teď nedohledám), kde autor považoval za velice důležitý parametr jazyka konzistentní hustotu informací (to je moje parafráze, jak to nazval on už si nepamatuji).  Šlo o to, aby nebylo na 3 řádcích "prázdno" a pak na jednom řádku složitá konstrukce. Tímhle nešvarem dle mého názoru trpí třeba Perl nebo Python, naopak jazyky jako Lua nebo Go jsou skoro až řídké (alespoň dle mých zkušeností). Nejzajímavější na tom je to, že ve výsledku nevidím, že by program v Go  nebo Lue byl delší než odpovídající program v v Perlu nebo Pythonu, skoro naopak.

Tahle ta konzistentní hustota je podle autora článku klíčová pro snadnou čitelnost/udržitelnost kódu.

Java
Kód: [Vybrat]
public class HelloWorld {
   public static void main(String[] args) {
      System.out.println("Hello, World");
   }
}

C
Kód: [Vybrat]
#include <stdio.h>
int main(){
   printf("Hello, World!");
   return 0;
}

Go
Kód: [Vybrat]
package main
import "fmt"
func main() {
    fmt.Println("hello world")
}

Python 3, Lua
Kód: [Vybrat]
print("Hello, World!")
Perl
Kód: [Vybrat]
print "Hello, World!\n";
Python 2
Kód: [Vybrat]
print "Hello, World!"

Python není jazyk vycpaný vatou, na to tu jsou jiní experti, třeba Java.
A i co se týče zápisu algoritmu není Python nijak zbytečně ukecaný a jde přímočaře k věci, oproštěn od všeho zbytečného. Následující jsem převzal z nějakého benchmarku jazyku.

Python 2
Kód: [Vybrat]
import sys

h, m = {}, 0
for l in sys.stdin:
    h[l] = h[l] + 1 if l in h else 1
    if  m < h[l]:
        m = h[l]
print len(h), m

Lua
Kód: [Vybrat]
local h, max, n = {}, 0, 0
for l in io.lines() do
    if (h[l]) then
        h[l] = h[l] + 1
    else
        n, h[l] = n + 1, 1
    end
    max = max > h[l] and max or h[l]
end
print(n, max)

Go
Kód: [Vybrat]
package main

import (
    "fmt"
    "bufio"
    "os"
)

func main() {
    r := bufio.NewReader(os.Stdin)
    h := make(map[string]int, 1e6)
    max := 1
    for {
        b, e := r.ReadSlice('\n')
        if e != nil {
            break
        }
        l := string(b)
        v := h[l] + 1
        h[l] = v
        if v > max {
            max = v
        }
    }
    fmt.Printf("%d\t%d\n", len(h), max)
}

Java
Kód: [Vybrat]
import java.util.HashMap;
import java.io.*;

class dict_v1 {
    public static void main(String[] args) {
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        HashMap<String, Integer> h = new HashMap<String, Integer>();
        String l;
        int max = 0;
        int x   = 0;
        while ((l = stdin.readLine()) != null) {
            if (h.containsKey(l)) {
                x = h.get(l) + 1;
                h.put(l, x);
                if (x > max){
                    max = x;
                }
            } else h.put(l, 1);
        }
        System.out.println(h.size()+" "+max);
    }
}

Ano, jsou to primitivní demonstrační příklady. Zajímavé to začne být ve chvíli, kdy chceme doplnit třeba unit testy o kterých je tu řeč. Python nabízí několikero možností, pro malé věci se nabízí třeba velmi pohodlný doctest. Následuje drobná úprava předešlého kódu:

Kód: [Vybrat]
import sys

def counter(lines):
    r"""
    >>> from cStringIO import StringIO as fwrap
    >>> counter(fwrap('a\nb\nb\n'))
    (2, 2)
    >>> counter(fwrap('a\nb\nb'))
    (3, 1)
    >>> counter(fwrap(''))
    (0, 0)
    >>> counter(fwrap('a a'))
    (1, 1)
    """
    h, m = {}, 0
    for l in lines:
        h[l] = h[l] + 1 if l in h else 1
        if  m < h[l]:
            m = h[l]
    return len(h), m

if  __name__ == '__main__':
    print counter(sys.stdin)

A takto se to použije:

python -m doctest -v dict-test.py
Trying:
    from cStringIO import StringIO as fwrap
Expecting nothing
ok
Trying:
    counter(fwrap('a\nb\nb\n'))
Expecting:
    (2, 2)
ok
Trying:
    counter(fwrap('a\nb\nb'))
Expecting:
    (3, 1)
ok
Trying:
    counter(fwrap(''))
Expecting:
    (0, 0)
ok
Trying:
    counter(fwrap('a a'))
Expecting:
    (1, 1)
ok
1 items had no tests:
    dict-test
1 items passed all tests:
   5 tests in dict-test.counter
5 tests in 2 items.
5 passed and 0 failed.
Test passed.


Bez verbose to vypisuje jenom chyby. Doctest lze separovat do pomocného txt souboru, pokud by někomu vadilo mít testy přímo v kódu. Pro složitější projekty pak python nabízí komplexnější modul unittest. Testy jsou důležité, ale důležité je také aby byly snadno použitelné, jinak je nikdo u menších věcí používat nebude.

Petr

Re:Typový system versus unittesty
« Odpověď #932 kdy: 28. 10. 2018, 14:36:56 »
A mimochodem, byl to právě doctest, kdo mě upozornil na to, že výstup z funkce se liší podle toho, zda je poslední řádek ukončen znakem \n nebo ne. Reálně bych to ve funkci ošetřil, aby na tom nezáleželo, ale nechtěl jsem do ní zasahovat. A tohle jde už imho za hranici možností statických typů. Tedy premisa tohoto téma - dobrý typový systém činí unit testy zbytečnými, je dle mého vyvrácena.

Re:Typový system versus unittesty
« Odpověď #933 kdy: 28. 10. 2018, 15:20:27 »
...
Tedy premisa tohoto téma - dobrý typový systém činí unit testy zbytečnými, je dle mého vyvrácena.
Myslis, ze nektery z tech jazyku ve tvych ukazkach ma dobry typovy system?

.

Re:Typový system versus unittesty
« Odpověď #934 kdy: 28. 10. 2018, 15:52:57 »
Mel bys priklad? Zajimal by me ten nesvar u perlu nebo pythonu... jak to vypada.
Než jsem stačil odpovědět, tak jsou tady příklady. Pokud vezmu uvedený příklad
Kód: [Vybrat]
import sys

h, m = {}, 0
for l in sys.stdin:
    h[l] = h[l] + 1 if l in h else 1
    if  m < h[l]:
        m = h[l]
print len(h), m
tak řádek
Kód: [Vybrat]
h[l] = h[l] + 1 if l in h else 1 má významně vyšší hustotu informací než ostatní řádky. Neříkám, že je špatně, v Pythonu je to celkem idiomatická konstrukce, jen to vypichuju jako ten příklad.


Kit

Re:Typový system versus unittesty
« Odpověď #935 kdy: 28. 10. 2018, 15:56:19 »
Zajímavé to začne být ve chvíli, kdy chceme doplnit třeba unit testy o kterých je tu řeč. Python nabízí několikero možností, pro malé věci se nabízí třeba velmi pohodlný doctest.

python -m doctest -v dict-test.py

Bez verbose to vypisuje jenom chyby. Doctest lze separovat do pomocného txt souboru, pokud by někomu vadilo mít testy přímo v kódu. Pro složitější projekty pak python nabízí komplexnější modul unittest. Testy jsou důležité, ale důležité je také aby byly snadno použitelné, jinak je nikdo u menších věcí používat nebude.

Díky, o této možnosti jsem netušil. Pokud něco budu psát v Pythonu, určitě to použiji.

Kit

Re:Typový system versus unittesty
« Odpověď #936 kdy: 28. 10. 2018, 15:59:07 »
řádek
Kód: [Vybrat]
h[l] = h[l] + 1 if l in h else 1 má významně vyšší hustotu informací než ostatní řádky. Neříkám, že je špatně, v Pythonu je to celkem idiomatická konstrukce, jen to vypichuju jako ten příklad.

To by vysvětlovalo, proč mnozí vývojáři nemají rádi SQL. Také má významně vyšší hustotu informací než ostatní řádky.

Re:Typový system versus unittesty
« Odpověď #937 kdy: 28. 10. 2018, 16:00:30 »
Java
Kód: [Vybrat]
import java.util.HashMap;
import java.io.*;

class dict_v1 {
    public static void main(String[] args) {
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        HashMap<String, Integer> h = new HashMap<String, Integer>();
        String l;
        int max = 0;
        int x   = 0;
        while ((l = stdin.readLine()) != null) {
            if (h.containsKey(l)) {
                x = h.get(l) + 1;
                h.put(l, x);
                if (x > max){
                    max = x;
                }
            } else h.put(l, 1);
        }
        System.out.println(h.size()+" "+max);
    }
}

A nedalo by se to napsat treba takhle?
Kód: [Vybrat]
    public static void main(String[] args){
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        Map<String, Integer> h = new HashMap<>();
        stdin.lines().forEach(l -> h.compute(l, (k, v) -> v == null ? 1 : v + 1));
        System.out.println(h.size() + " " + h.values().stream().max(Integer::compare).orElse(0));
    }

Ja vim, ze je to furt skaredy, a kdybych to dostal na code review tak bych k tomu mel minimalne 3 komentare, ale ...

Re:Typový system versus unittesty
« Odpověď #938 kdy: 28. 10. 2018, 16:09:24 »
Mel bys priklad? Zajimal by me ten nesvar u perlu nebo pythonu... jak to vypada.
Než jsem stačil odpovědět, tak jsou tady příklady. Pokud vezmu uvedený příklad
Kód: [Vybrat]
import sys

h, m = {}, 0
for l in sys.stdin:
    h[l] = h[l] + 1 if l in h else 1
    if  m < h[l]:
        m = h[l]
print len(h), m
tak řádek
Kód: [Vybrat]
h[l] = h[l] + 1 if l in h else 1 má významně vyšší hustotu informací než ostatní řádky. Neříkám, že je špatně, v Pythonu je to celkem idiomatická konstrukce, jen to vypichuju jako ten příklad.

OK. Rozumim. Diky

Re:Typový system versus unittesty
« Odpověď #939 kdy: 28. 10. 2018, 17:51:59 »
Java
Kód: [Vybrat]
import java.util.HashMap;
import java.io.*;

class dict_v1 {
    public static void main(String[] args) {
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        HashMap<String, Integer> h = new HashMap<String, Integer>();
        String l;
        int max = 0;
        int x   = 0;
        while ((l = stdin.readLine()) != null) {
            if (h.containsKey(l)) {
                x = h.get(l) + 1;
                h.put(l, x);
                if (x > max){
                    max = x;
                }
            } else h.put(l, 1);
        }
        System.out.println(h.size()+" "+max);
    }
}

A nedalo by se to napsat treba takhle?
Kód: [Vybrat]
    public static void main(String[] args){
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        Map<String, Integer> h = new HashMap<>();
        stdin.lines().forEach(l -> h.compute(l, (k, v) -> v == null ? 1 : v + 1));
        System.out.println(h.size() + " " + h.values().stream().max(Integer::compare).orElse(0));
    }

Ja vim, ze je to furt skaredy, a kdybych to dostal na code review tak bych k tomu mel minimalne 3 komentare, ale ...

Mozna jeste elegantnejsi zapis:
Kód: [Vybrat]
    public static void main(String[] args){
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        Map<String, Integer> h = stdin.lines().collect(Collectors.toMap(k -> k, v -> 1, (i, j) -> i + j));
        System.out.println(h.size() + " " + h.values().stream().max(Integer::compare).orElse(0));
    }

gll

  • ****
  • 429
    • Zobrazit profil
    • E-mail
Re:Typový system versus unittesty
« Odpověď #940 kdy: 28. 10. 2018, 18:20:58 »
v pythonu přesně na tohle existuje specializovaná kolekce Counter, když už se bavíte o idiomatickém kódu

Kód: [Vybrat]
import sys
from collections import Counter

c = Counter(sys.stdin)
print(len(c), c.most_common(1)[0][1])
« Poslední změna: 28. 10. 2018, 18:23:06 od gll »

Petr

Re:Typový system versus unittesty
« Odpověď #941 kdy: 29. 10. 2018, 05:23:38 »
...
Tedy premisa tohoto téma - dobrý typový systém činí unit testy zbytečnými, je dle mého vyvrácena.
Myslis, ze nektery z tech jazyku ve tvych ukazkach ma dobry typovy system?
Záleží na definici dobrého typového systému. Pokud je to takový, že dělá unit testy zbytečnými, pak jazyk s dobrým typovým systémem neexistuje.

Petr

Re:Typový system versus unittesty
« Odpověď #942 kdy: 29. 10. 2018, 05:35:39 »
Mel bys priklad? Zajimal by me ten nesvar u perlu nebo pythonu... jak to vypada.
Než jsem stačil odpovědět, tak jsou tady příklady. Pokud vezmu uvedený příklad
Kód: [Vybrat]
import sys

h, m = {}, 0
for l in sys.stdin:
    h[l] = h[l] + 1 if l in h else 1
    if  m < h[l]:
        m = h[l]
print len(h), m
tak řádek
Kód: [Vybrat]
h[l] = h[l] + 1 if l in h else 1 má významně vyšší hustotu informací než ostatní řádky. Neříkám, že je špatně, v Pythonu je to celkem idiomatická konstrukce, jen to vypichuju jako ten příklad.

Imho nemá vyšší informační hustotu, než třeba ve většině jazyků běžný for( ; ; ), takže se to ničemu běžnému nevymyká. Zajímavé je, že tě nezaujalo hůře čitelné 'max = max > h[l] and max or h[l]' u jazyku Lua, který dáváš za vzor jazyka, který má údajně hustotu rovnoměrnou. Pokud je řádek krátký a dobře čitelný, není jednořádkový if na škodu, právě naopak, dělá program přehlednější a pro složitější podmínky má python if klasický, nezatížený vatou prázdných řádků se závorkou, jak je ostatně vidět o řádek níž.

Petr

Re:Typový system versus unittesty
« Odpověď #943 kdy: 29. 10. 2018, 05:40:16 »
Java
Kód: [Vybrat]
import java.util.HashMap;
import java.io.*;

class dict_v1 {
    public static void main(String[] args) {
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        HashMap<String, Integer> h = new HashMap<String, Integer>();
        String l;
        int max = 0;
        int x   = 0;
        while ((l = stdin.readLine()) != null) {
            if (h.containsKey(l)) {
                x = h.get(l) + 1;
                h.put(l, x);
                if (x > max){
                    max = x;
                }
            } else h.put(l, 1);
        }
        System.out.println(h.size()+" "+max);
    }
}

A nedalo by se to napsat treba takhle?
Kód: [Vybrat]
    public static void main(String[] args){
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        Map<String, Integer> h = new HashMap<>();
        stdin.lines().forEach(l -> h.compute(l, (k, v) -> v == null ? 1 : v + 1));
        System.out.println(h.size() + " " + h.values().stream().max(Integer::compare).orElse(0));
    }

Ja vim, ze je to furt skaredy, a kdybych to dostal na code review tak bych k tomu mel minimalne 3 komentare, ale ...
Dalo, ale je to hůře čitelné a srozumitelné. Cíl není nejmenší počet řádků, ale nejvyšší přehlednost a čitelnost, aby se programator nedopouštěl chyb z nepozornosti a nepochopení.

Re:Typový system versus unittesty
« Odpověď #944 kdy: 29. 10. 2018, 08:40:40 »
Java
Kód: [Vybrat]
import java.util.HashMap;
import java.io.*;

class dict_v1 {
    public static void main(String[] args) {
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        HashMap<String, Integer> h = new HashMap<String, Integer>();
        String l;
        int max = 0;
        int x   = 0;
        while ((l = stdin.readLine()) != null) {
            if (h.containsKey(l)) {
                x = h.get(l) + 1;
                h.put(l, x);
                if (x > max){
                    max = x;
                }
            } else h.put(l, 1);
        }
        System.out.println(h.size()+" "+max);
    }
}

A nedalo by se to napsat treba takhle?
Kód: [Vybrat]
    public static void main(String[] args){
        BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
        Map<String, Integer> h = new HashMap<>();
        stdin.lines().forEach(l -> h.compute(l, (k, v) -> v == null ? 1 : v + 1));
        System.out.println(h.size() + " " + h.values().stream().max(Integer::compare).orElse(0));
    }

Ja vim, ze je to furt skaredy, a kdybych to dostal na code review tak bych k tomu mel minimalne 3 komentare, ale ...
Dalo, ale je to hůře čitelné a srozumitelné. Cíl není nejmenší počet řádků, ale nejvyšší přehlednost a čitelnost, aby se programator nedopouštěl chyb z nepozornosti a nepochopení.
1. Zalezi na tom co ses zvyklej cist. Kdyz sem poprve videl lambdu tak mi to taky neslo prez rohovku.
2. Zajimave je, ze v te tve javove verzi jsou dve chyby z nepozornosti...