Čitelný kód – syntaxe vs. sémantika

K tomuto (možná) trochu záhadně nadepsanému článku mne přivedla kniha Dokonalý kód od Steve McConnella a přednáška Doporučené postupy v programování, kterou navštěvuji na MFF. Jedná se o úvahu nad stylem psaní srozumitelného kódu.

Úvod

Syntax (v informatice) jsou, podle české wikipedie, pravidla pro zápis formálního jazyka. S tímto významem je asi každý programátor seznámen, už jen díky hláškám kompilátoru začínajícím slovy „Syntax error on line…“. Sémantika je nauka o významu. Syntakticky nesprávný program nepůjde přeložit. Sémanticky nesprávný program nebude dělat to, co by měl. Napsat syntakticky správný program je rutinní záležitost, ale napsat kód sémanticky správně už zdaleka ne, a je to právě ta věc, která programování činí netriviálním úkolem.

Přidejme do kódu maximum informace o našem záměru

Správnost syntaxe nám ohlídá kompilátor, ale sémantiku už si musíme hlídat sami. Z toho plyne, že pro srozumitelný kód je nejdůležitější co nejlépe vyjádřit právě sémantiku a to sémantiku z pohledu problémové domény.

double a = p * 0.1667;

Toto je ze syntaktického hlediska naprosto validní kód, ale dokázali byste říci něco o jeho sémantice-významu? Někteří možná namítnou, že pokud by viděli zbývající řádky kódu, význam proměnné a by jistě pochopili, ale to je ten problém! Srozumitelný kód je takový, který všemi možnými prostředky umožní čtenáři pochopit jeho sémantický význam tak, aby nad ním musel co nejméně přemýšlet. Přitom mezi „všemi možnými prostředky“ by komentáře měly být až na jednom z posledních míst, zatímco syntaktické možnosti použitého jazyka na místech prvních. Konzistentní název jedné proměnné si vynutí překladač, konzistentní komentáře nikoliv. Následující ukázka, podle mého názoru, ze sémantického hlediska odhalí daleko víc.

double vat = productPrice * VatCoefficient;

Kdyby někdo z nepozornosti napsal plus místo krát, při hledání chyby by pravděpodobně stačil pouze a jen pohled na tento řádek, to se o první ukázce rozhodně říct nedá.

Syntaxe není z hlediska problémové domény relevantní

Správné pojmenování proměnných, použití konstanty, to jsou věci, které by každému programátorovi měly být už jasné, říkáte si. Co další ukázka?

var tasks = this.tasksService.GetTasks();
var users = new List<User>(this.userService.GetUsers());

if (users.Count(u => u.IsAdmin) == 0) {
  users.Add(new User("admin") { IsAdmin = true });
}

foreach(var task in tasks) {
  task.isDone = true;
}

taskService.UpdateAll(tasks);
userService.UpdateAll(users);

Zde se programátor nechal unést více syntaktickým hlediskem-seskupení přiřazení a volání metody UpdateAll-, než významem z pohledu řešeného problému. Lepší varianta rozděluje kód na části z významového hlediska a přímo vybízí k správnému refactoringu-extrahování 2 metod, které půjdou jednoduše pojmenovat bez spojky „and“.

var tasks = this.tasksService.GetTasks();
foreach(var task in tasks) {
  task.isDone = true;
}
taskService.UpdateAll(tasks);

var users = new List<User>(this.userService.GetUsers());
if (users.Count(u => u.IsAdmin) == 0) {
  // Ta podmínka v ifu ale také není úplně ideální, viz komentáře.
  users.Add(new User("admin") { IsAdmin = true });
}
userService.UpdateAll(users);

Závěr

Možná vám přišly ukázkové příklady jasné a myslíte si, že „ukázkově špatný“ kód byste nenapsali, ale to byly jen ukázky v článku. Věděli jste, co máte očekávat a hledat. Když budete příště psát nějaký kód, např.

IEnumerable<User> GetAllUsers(bool adminFlag)

zkuste si říct, jakou sémantickou informaci navíc přidává toto pojmenování parametru, nevytvářím jméno spíše podle syntaktického hlediska? Co mi slovo „flag“ řekne navíc? Každý bool je svým způsobem „flag“. Nešlo by toho prostřednictvím pojmenování parametru říct čtenáři kódu víc? Která z těchto dvou hlaviček metody odpovídá té předchozí svým významem?

IEnumerable<User> GetAllUsers(bool selectAdmins)
IEnumerable<User> GetAllUsers(bool skipAdmins)

Může se to zdát triviální a možná právě kvůli tomu na to člověk občas zapomene. Na úplný závěr přidám jako odstrašující případ výpis obsahu složky „actions“ jednoho PHP projektu, který jsem byl nucen upravit. Ještě, že autor soubory pojmenoval prefixem „action“, jinak bych totiž vůbec netušil, že to jsou akce. BAZINGA.

total 34
----------+ 1 steve None   404 Mar  9 21:18 action_1.php
----------+ 1 steve None  1653 Mar  9 21:18 action_2.php
----------+ 1 steve None  1638 Mar  9 21:18 action_3.php
----------+ 1 steve None   648 Mar  9 21:18 action_4.php
----------+ 1 steve None  2202 Mar  9 21:18 action_5.php
----------+ 1 steve None   910 Mar  9 21:18 action_6.php
----------+ 1 steve None 12619 Mar  9 21:18 action_7.php

Comments 2

  1. Aleš Roubíček wrote:

    Když už jsme u zlepšování čitelnosti, proč používat extenzi Count == 0, když tu máme Any == false, které lépe vyjadřuje náš záměr? :)

    Posted 12 Dub 2010 at 6.45
  2. admin wrote:

    Ano, díky za upozornění. Extension metodu Any jsem neznal, člověk se pořád učí :) .

    Mimochodem Any() == false vs. !Any() je taky zajímavé téma ohledně čitelnosti, pascalské (basicovské) Not by bylo nejlepší.

    Když nad tím zpětně přemýšlím: v duchu „vyjádření sémantického významu“ by asi bylo nejlepší udělat si vlastní (extension) metodu IsEmpty() (nebo ještě líp: ContainsAdmin()), která by skryla jak implementační detail v podobě porovnání počtu s nulou, tak negaci, a navíc by nejzřetelněji vyjadřovala, co se vlastně v ifu testuje.

    Škoda, že ji v .NET BCL nemáme (nebo přijde další překvapení(?)).

    Posted 12 Dub 2010 at 13.14

Post a Comment

Your email is never published nor shared. Required fields are marked *