Spustit jako prezentaci

Doporučené postupy v programování

Cvičení

Původní slajdy: David MajdaCvičení: Dominik Škoda, Petr Kubát

MFF UK

Jak budou probíhat cvičení?

Hodnocení

  1. úloha: max. 10 pts + 10 pts (oprava)
  2. úloha: max. 10 pts
  3. úloha: max. 10 pts
  4. úloha: max. 40 pts
  5. úloha: max. 10 pts + 10 pts (rozšíření)
Body Známka
> 80 1
> 65 2
> 50 3
≤ 50

"Routine from hell"

"Routine from hell"

void HandleStuff( CORP_DATA & inputRec, int crntQtr,
   EMP_DATA empRec, double & estimRevenue, double ytdRevenue,
   int screenX, int screenY, COLOR_TYPE & newColor,
   COLOR_TYPE & prevColor, StatusType & status, int expenseType )
{

int i;
for ( i = 0; i < 100; i++ ) {
   inputRec.revenue[i] = 0;
   inputRec.expense[i] = corpExpense[ crntQtr ][ i ];
   }
UpdateCorpDatabase( empRec );
estimRevenue = ytdRevenue * 4.0 / (double) crntQtr;
newColor = prevColor;
status = SUCCESS;

if ( expenseType == 1 ) {
     for ( i = 0; i < 12; i++ )
           profit[i] = revenue[i] - expense.type1[i];
     }
else if ( expenseType == 2 ) {
          profit[i] = revenue[i] - expense.type2[i];
          }

else if ( expenseType == 3 )
          profit[i] = revenue[i] - expense.type3[i];
          }
	

Co je na tomto kódu špatně? Podle Steve McConnella:

  • The routine has a bad name. HandleStuff() tells you nothing about what the routine does.
  • The routine isn't documented.
  • The routine has a bad layout. The physical organization of the code on the page gives few hints about its logical organization. Layout strategies are used haphazardly, with different styles in different parts of the routine. Compare the styles where expenseType == 2 and expenseType == 3.
  • The routine's input variable, inputRec, is changed. If it's an input variable, its value should not be modified (and in C++ it should be declared const). If the value of the variable is supposed to be modified, the variable should not be called inputRec.
  • The routine reads and writes global variables it reads from corpExpense and writes to profit. It should communicate with other routines more directly than by reading and writing global variables.
  • The routine doesn't have a single purpose. It initializes some variables, writes to a database, does some calculations – none of which seem to be related to each other in any way. A routine should have a single, clearly defined purpose.
  • The routine doesn't defend itself against bad data. If crntQtr equals 0, the expression ytdRevenue * 4.0 / (double) crntQtr causes a divide-by-zero error.
  • The routine uses several magic numbers: 100, 4.0, 12, 2, and 3.
  • Some of the routine's parameters are unused: screenX and screenY are not referenced within the routine.
  • One of the routine's parameters is passed incorrectly: prevColor is labeled as a reference parameter (&) even though it isn't assigned a value within the routine.
  • The routine has too many parameters. The upper limit for an understandable number of parameters is about 7; this routine has 11. The parameters are laid out in such an unreadable way that most people wouldn't try to examine them closely or even count them.
  • The routine's parameters are poorly ordered and are not documented.

A já dodávám:

  • Názvy parametrů a lokálních proměnných používají zkratky.
  • Názvy typů v parametrech jsou nekonzistentní (_TYPE × _DATA) a kryptické.
  • Deklarace řídící proměnné cyklu (int i) by měla být v cyklu samotném.
  • Testy hodnoty parametru expenseType by měly být realizovány příkazem switch.

Na jazyku záleží

Znáte "svůj" jazyk?

Příklad: obchodník

Zadání

Napište program, který vypíše kolik vám má vrátit obchodník, když kupujete rohlík za $1.10 a zaplatíte $2 bankovkou.

Ukázat kód presentation/Change.java, tipování co je správně, spustit...

Příklad: obchodník

Ponaučení

Pozor na čísla s plovoucí čárkou.

Příklad: sčítání

Zadání

Napište program, který sečte 2 čísla. :-)

Ukázat kód presentation/Elementary.java, tipování výsledku, spustit...

Příklad: sčítání

Ponaučení

Vyhýbejte se konstrukcím, které nefungují, jak to na první pohled vypadá.

Příklad: sčítání 2

Zadání

Napište program, který sečte 2 čísla v 16-tkové soustavě. ;-)

Ukázat kód presentation/NiceHex.java, tipování výsledku, spustit... Po předchozím příkladě budou studenti o něco podezíravější... Za výsledek je zodpovědný dvojkový doplněk, neboť cafebabe je záporné číslo, a když se prodlouží z int na long, doplní se to jedničkami zleva. Potom ta jednička v prvním čísle uletí mimo přesnost.

Inherentní × zavlečená složitost

Inherentní × zavlečená složitost

Inherentní složitost

Zavlečená složitost

"Barevný kód"

Příklad: Počítání zaměstnanců

Zadání

Dána kolekce všech zaměstnanců. Napište program, který určí počet vývojářů se mzdou vyšší než 20&nbsp000.

Příklad: Počítání zaměstnanců

Řešení (C#)

var count = 0;
foreach (var employee in employees)
{
    if (employee.Position == PositionType.Developer
        && employee.Salary > 20_000)
    {
      count++;
    }
}
  • Jde o klasické "Javovské" řešení problému - je v pořádku, používá se foreach a IEnumerable, nemáme umělé iterační proměnné, nehrozí +- 1 chyby.
  • Ovšem v dnešních verzích C# by to šlo napsat i jinak a možná lépe...

Příklad: Počítání zaměstnanců

Řešení (C#)

var count = 0;
foreach (var employee in employees)
{
    if (employee.Position == PositionType.Developer
        && employee.Salary > 20_000)
    {
      count++;
    }
}

Příklad: Počítání zaměstnanců

Řešení (C# bez LINQu)

var count = 0;
foreach (var employee in employees)
{
    if (employee.Position == PositionType.Developer
        && employee.Salary > 20_000)
    {
      count++;
    }
}

Řešení (C# s LINQem)


employees
  .Where(e => e.Position == PositionType.Developer && e.Salary > 20_000)
  .Count();
          
  • LINQ nabízí higher order funkce pro transformace a agregace kolekcí (a enumerable objektů obecně). Java už má analogickou featuru, streams. Většina moderních jazyků už tento funkcionální přístup nabízí taky.
  • Obecně je dobré používat higher-order funkce pro transformace datových kolekcí - zvyšuje to čitelnost a výrazně odbourává zavlečenou složitost.

Příklad: Počítání zaměstnanců

Řešení (C# bez LINQu)

var count = 0;
foreach (var employee in employees)
{
    if (employee.Position == PositionType.Developer
        && employee.Salary > 20_000)
    {
      count++;
    }
}

Řešení (C# s LINQem)


employees
  .Where(e => e.Position == PositionType.Developer && e.Salary > 20_000)
  .Count();
        

Příklad: Součet čísel od 1 do 100

Zadání

Napište program, který sečte čísla od 1 do 100.

Nějaký nebohý student napíše tento program na tabuli ve svém oblíbeném programovacím jazyce, který dozajista bude něco Java, C# nebo C/C++. Jiný nebohý student se následně pokusí vyznačit v kódu různé druhy složitosti.

Vtipné bude, pokud dotyčný student napíše vzoreček místo for-cyklu.

Zdroj inspirace: článek Reginalda Braithwaita, diskuze na Redditu

Příklad: Součet čísel od 1 do 100

Řešení (Ruby)

(1..100).inject(&:+)
V programovacích jazycích, jejichž názvy metod nevymýšleli Japonci neumějící pořádně anglicky, je metoda inject známá jako fold nebo reduce.

Příklad: Součet čísel od 1 do 100

Řešení (Ruby)

(1..100).inject(&:+)

Příklad: Součet čísel od 1 do 100

Řešení (Ruby)

(1..100).inject(&:+)

Řešení (Haskell)

sum [1..100]

Příklad: Součet čísel od 1 do 100

Řešení (Ruby)

(1..100).inject(&:+)

Řešení (Haskell)

sum [1..100]

Příklad: Součet čísel od 1 do 100

Řešení (Ruby)

(1..100).inject(&:+)

Řešení (Haskell)

sum [1..100]

Řešení (Kotlin)

(1..100).sum()

Příklad: Součet čísel od 1 do 100

Řešení (Ruby)

(1..100).inject(&:+)

Řešení (Haskell)

sum [1..100]

Řešení (Kotlin)

(1..100).sum()

Závěr posledních příkladů

Language matters

  • Důsledek: Při psaní použít "nejvyšší" možný jazyk (dle úrovně abstrakce/expresivity).
  • Nejexpresivnější jsou typicky různé skriptovací jazyky (Perl, Python, Ruby,...). Pokud žádný neumíte, naučte se.
  • Dnes je trendem pronikání těchto featur i do moderních staticky typovaných jazyků. Podívejte se na Scalu (viz předmět Koncepty moderních programovacích jazyků) či Kotlin.

Příklady

Jak má a NEMÁ kód vypadat

Ukázat zdrojáky - adresář awfull ukazuje fakt škaredý kód:
  • bad_class - třída která dělá vše a nic, hodně nekomentovaných metod.
  • bad_function - obsahuje např. switch/for ...
  • bad_globals - obsahuje proměnné buffer1-4, které se používají náhodně v celém zdrojáku.
  • bad_whole_file - vše špatně, na jaký antipattern si vzpomenete, ten tam asi najdete :)
  • bad_malloc - malloc ze starého kalista.
Adresář nice ukazuje, jak má kód vypadat.
  • nice_malloc je nová verze mallocu z kalista přepsaná od Petra Tůmy.
  • nice_class je pěkná třída od Luboše, plná komentářů a krátkých metod.

Cvičení

Co je na tom kódu špatně?

Cvičení

Co je na tom kódu špatně?

Nulování matice pod diagonálou

	void clear_below_diagonal(double matrix[][] ){
		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				if (j < i) {
					matrix[j][i] = 0;
				}
			}
		}
	}
        
Špatně jsou mimo jiných 2 věci - názvy i a j, plus jsou přehozené buď v podmínce nebo v přiřazení (záleží na definici co je řádek a co sloupec matice = pokud jdou i-čka po sloupcích, je to v pořádku).

Cvičení

Co je na tom kódu špatně?

Nulování matice pod diagonálou

	void clear_below_diagonal(double matrix[][] ){
		for (int row = 0; row < matrix.length; row++) {
			for (int column = 0; column < matrix[row].length; column++) {
				if (column < row) {
					matrix[row][column] = 0;
				}
			}
		}
	}
        
Toto je více srozumitelné a i se zde snáze najde (předchozí již opravená) chyba.

Cvičení

Co je na tom kódu špatně?

Násobení prvků pole

	void multiply(int array[], int x) {
		for ( int i = 0; i < array.length; i++) {
			array[i] *= x;
		}
	}
        
i je v tomto příkladě v pořádku = krátké + jasné + konvencne, x je špatně.

Cvičení

Co je na tom kódu špatně?

Výpočet ceny nákupu včetně DPH

	double calculate_total_price(double items_price[], int category[]) {
		double sum = 0;
		for (int i = 0; i < items_price.length; i++) {
			if (category[i] == 0) {
				sum += items_price[i];
			} else if (category[i] == 1) {
				sum += items_price[i] * 1.14;
			} else if (category[i] == 2) {
				sum += items_price[i] * 1.2;
			}
		}
		
		return sum;
	}
        
Špatně je mnoho - konstanty, items by měly být objekty, chybí kontroly délky, ...

Odbočka

WFTs/minute