Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Nepište zbytečný kód

07.19 - 3. srpna 2009 | ASP.NET 2.0

Jedním ze základních pravidel psaní přehledného kódu je nepsat zbytečný kód. Pojďme si ukázat pár příkladů zbytečného kódu, kterému je zahodno se raději vyhnout.

Komentáře

Programátoři jsou často velice kreativní lidé, ale svou kreativitu si vybíjí nesprávným způsobem. Tak se může stát, že během své kariéry narazíte na skvosty typu:

  1. zakomentované kusy kódu
  2. označení dočasného kódu v kometáři
  3. vylévání srdíčka nad vlastní blbostí

K bodu prvnímu. Když něco takového potkáte, rovnou to smažte, nesnažte se nad tím přemýšlet. Jestli někdy ten kód budete potřebovat, najdete ho ve vašem VCS. Pokud se přistihnete, že máte označený blok kódu a chystáte se zmáčknout magickou kombinaci kláves Ctrl+K, C, ušetřete si práci a rovnou zmáčkněte Delete. ;)

K bodu druhému. Nejprve zjistěte, kdo to napsal a dejtemu rovnou pohlavek nebo seberte prémie. Nejlépe oboje, u někoho lépe fungují podmíněné reflexy spojené s fyzickou bolestí, někdo si vzpomene, že mu nezbyly peníze na tu skvělou chlastačku a už to nebude chtít opakovat. Zjišťovat, co na koho platí je moc drahé. ;)

Pro představu, našli jsme takovýto kód:

//Testovaci verze
if (Request.Cookies.AllKeys.Contains("ticket")) {
  InvitedUser iuser = _invitedUsersRepository.FindByHash(Request.Cookies["ticket"].Value);
  if (iuser != null) {
    iuser.Valid = false;
    _invitedUsersRepository.Save(iuser);
  }
}
// --
;

Autora jsme ztrestali. První krok máme za sebou, co dál? Tohle asi hned smazat nepůjde. Prvním možným krokem, je nadefinovat build symbol a tyto bloky obalit preprocesorovou podmínkou:

#if TEST_VERSION
if (Request.Cookies.AllKeys.Contains("ticket")) {
  InvitedUser iuser = _invitedUsersRepository.FindByHash(Request.Cookies["ticket"].Value);
  if (iuser != null) {
    iuser.Valid = false;
    _invitedUsersRepository.Save(iuser);
  }
}
#endif
;

Sice jsme se nezbavili špatného kódu, ale aspoň pujde dočasná featura vypnout z jednoho místa a kód hovoří trochu jasněji.

Pokud napíšete blok kódu a pocítíte potřebu uvodit ho komentářem, nebo takových bloků máte zasebou víc, je čas refaktorovat! Místo komentářů vytvořte metody s popisnými názvy (ekvivalent komentáře). Komentáře mají tu vlastnost, že rychle ztrácejí význam a zůstávají v místech, kde už není co komentovat.

Pravidlo: Když ucítíte potřebu napsat v kódu komentář, zamyslete se, zda nenajdete lepší způsob jak tuto myšlenku vyjádřit.

Poznejte svůj jazyk

Za programátora se považuje každý, kdo se v nějakém jazyce naučí pár příkazů. Někdo si vystačí s málem a pak mu nezbývá než prasit. Základem je poznat jazyk, ve kterém se chystáme sdělovat své myšlenkové pochody. Zdrojový kód se mnohem častěji čte než píše, proto by měl obsahovat co nejméně zbytečností, které odvádějí pozornost.

Začněme třeba u JavaScriptu. Často se setkávám s kódem, který napovídá, že autor má hrubé znalosti C a jemu podobných jazyků, ale o JavaScriptu moc potuchy nemá.

Podmíněné přiřazení

Jednou z typických ukázek může být validace vstupních parametrů a nastavení výchozí hodnoty, pokud přišlo null. Nejhorší případ:

function example(options) {
  if (options == null) {
    this.options = { color: 'red' };
  }
  else {
    this.options = options;
  }
}

O něco chytřejší programátor použije ternární podmíněný operátor:

function example(options) {
  this.options = (options != null) ? options : { color: 'red' };
}

Vývojář znalý JavaScriptu však odbourá zbytečné ruchy a vznikne logický zápis:

function example(options) {
  this.options = options || { color: 'red' };
}

Pojďme teď dál k mému oblíbenému C#. Předchozí příklad tam nejde řešit operátorem || protože C# je silně typový a null != false. Ovšem C# 2.0 přinesl s Nullable<T> hodnotovými typy i operátor ??. Ten slouží jako zkrácená varianta ternárního podmíněného operátoru. Díky němu můžeme následující kód:

public void Example(Options options) {
  _options = (options != null) ? options : new Options { Color = Color.Red };
}

Pěkně odšumět:

public void Example(Options options) {
  _options = options ?? new Options { Color = Color.Red };
}

Pokud pracujete s Nullable hodnotovými typy, zkraťte i následující zá­pis:

public void Example(int? pageIndex) {
  int page = 1 + (pageIndex.HasValue ? pageIndex.Value : 0);
}

Na:

public void Example(int? pageIndex) {
  int page = 1 + (pageIndex ?? 0);
}

Generické parametry

Dalším, velice častým šumem, je explicitní uvádění generických parametrů, tam kde to není potřeba. Za příklad si vezměme třeba generickou metodu Assert.Equal<T>(T expected, T actual) z frameworku xUnit.net. Můžeme ji volat takto:

Assert.Equal<string>("test", null);

Jenže kompilátor není žádný hlupák a dokáže správně dosadit typ generického parametru podle typu předávaného parametru. Proto je možné napsat, mnohem přehlednější zápis:

Assert.Equal("test", null);

FxCop vám přímo radí: Pište generické metody tak, aby se typ generického parametru dal implicitně odvodit.

Zbytečné volání ToString

Často se také můžete setkat s voláním metody ToString tam, kde to není pořeba. Příklad:

double rating = 3.8;
string output = "Your rating is: " + rating.ToString();

Tohle lze napsat bez šumu:

double rating = 3.8;
string output = "Your rating is: " + rating;

Teď si jistě, říkáte, že jsem asi blbec, jak můžu po silně typovém C# chtít, aby sečetl string a double. Nuže můžu. Krom toho, že je C# silně typový, podporuje i přetěžování operátorů! A designéři třídy String přidali přetížení operátoru + na kterýkoliv Object, na kterém zavolají metodu ToString za vás. :)

Zbytečné explicitní přetypování

Občas se bohužel můžeme v kódu potkat s věcmi, nad kterými zůstává rozum stát. Někdo například už pochopil dědičnost. Pochopil, že třída List<T> implementuje rozhraní IList<T> a to dědí z ICollection<T>, které je potomkem obecného rozhranní IEnumerable<T>. Jenže pak přijde na praktické použití a je zle:

public class UserDetailViewData {
  public IEnumerable<User> Friends { get; set; }
}

public class UsersController : Controller {
  public ActionResult Detail(userName) {
    IList<User> friends = GetFriendsOfUser(userName);
    return View(new UserDetailViewData {
      Friends = (IEnumerable<User>)friends
    });
  }
}

Takhle vytržené z kontextu to nemusí vypadat až tak zle, ale originál mě stál málem ukroucení hlavy.

Roztahaná inicializace

Novinkou C# 3.0 jsou inicializátory. Zvažte následující kód:

var user = new User();
user.Name = "Josef Novák";
user.Gender = Gender.Male;

Přepsat na:

var user = new User {
  Name = "Josef Novák",
  Gender = Gender.Male,
};

Vyhnete se tím, zbytečnému opakování user..

Závorky navíc

Častý šumem v kódy bývají také závorky, které nemají žádný význam, zejména u inicilizátorů s bezparametrickým konstrukotrem new User() { Name = "Josef Novák" } a u anonymních bezparametrických delegátů delegate() { return true; }. Tyto závorky klidně smažte a u delegátů zvažte kompresi na lambda výraz: ()=> true.

Na druhou stranu existuje spousta případů, kdy vhodně vložené závorky zvyšují čitelnost.

Závěr

Když říkám, že je lepší psát némě kódu, rozhodně tím nemám na mysli kriptické zápisy alá regexy. To, co ušetříte na zbytečném kódu, klidně investujte do popisných názvů proměnných a metod. Důležitá je čitelnost. Se spoustou výše zmiňovaných úprav vám mohou pomoci nástroje, např. CodeRush Express, který je zcela zdarma.

Rozhodně jsem nepokryl všechny možnosti, kde psát zbytečný kód navíc. Jistě taky některé znáte, podělte se o ně v komentářích. :)

Autor: Aleš Roubíček | Přidej komentář | Delicious | Digg | FriendFeed | Facebook | Linkuj! | Jagg

Komentáře RSS

  1.  

    Augi

    09.15 - 3. srpna 2009 | #

    • Většina typů má více než 3 písmenka, takže se dá ušetřit použitím varu. Taky si tím člověk občas ušetří trošku přemýšlení. Ale bacha, aby to nevedlo ke snížení čitelnosti…
    • inicializace polí – místo string[] pole = new string[] { "jedna", "dva" }; stačí napsat var pole = new [] { "jedna", "dva" };
    • blok using místo komba try-finally
  2.  

    Augi

    09.15 - 3. srpna 2009 | #

    Jaj, kdyžtak pošteluj prosím syntaxi u předchozího komentáře a smaž tenhle. Díky ;)

  3.  

    Aleš Roubíček

    09.59 - 3. srpna 2009 | #

    [2] Augi: inline kód můžeš psát následovně:

    `string[] pole = new string[] { "jedna", "dva" };`

    blokovej s barvičkama pak:

    /--code csharp
    string[] pole = new string[] { "jedna", "dva" };
    \--code
    
  4.  

    meap

    19.58 - 3. srpna 2009 | #

    [1] Augi: Podle mě je velmi nešťastné psát všude var jen z toho důvodu, že je kratší než většina typů. Čitelnost to naopak zhoršuje!

    for (var i = 0; i < list.Items.Count; i++){
        var aaa = list.Items[i];

    V tomto případě se např. nedozvím jakého typu je proměnná aaa. Podle mě by mělo platit to, že klíčové slovo var lze použít pouze v situaci, kdy jsem z pravé strany schopen vyčíst jakého typu, že je daná instance. Když už to tedy musí být.

    Nicméně podle mě by se to mělo používat jen v případě anonymních typů.

  5.  

    Aleš Roubíček

    21.46 - 3. srpna 2009 | #

    [4] meap: Pokud proměnnou aaa dobře pojmenuješ, tak by to mělo být víceméně jasné. Každopádně var používám jen tam, kde není napravo explicitně určený typ a ve foreach cyklech v APSX šablonách.

  6.  

    Augi

    08.20 - 4. srpna 2009 | #

    [4] meap:Však jsem sám psal, že použití varu může vést ke snížení čitelnosti…

  7.  

    meap

    09.05 - 4. srpna 2009 | #

    [6] Augi: Já vím, ale ta formulace Většina typů má více než 3 písmenka, takže se dá ušetřit použitím varu. mě nevyzněla jako zrovna nejšťastnější :-)

    [4] meap: Čekal jsem jestli s tímto argumentem někdo přijde, ale lepší by ve zmíněném příkladu bylo pojmenovat konkrétněji ten list, např. feedList. Nicméně i přesto si myslím to co jsem napsal.

  8.  

    Augi

    13.30 - 4. srpna 2009 | #

    [7] meap:Však to bylo myšleno jen jako nadsázka :)

  9.  

    veena

    15.13 - 6. srpna 2009 | #

    Tenhle blog je čim dál lepší!

    Ještě pár let a Joel Spolsky bude mít konkurenta :-)

    BTW, to fakt používáte v .NETu ty 2 mezery na odsazení? Tohle mi samo přijde dost znepřehledňující čtení. 4 mezery kód, 2 mezery html mi přijde optimální.

  10.  

    Aleš Roubíček

    16.29 - 6. srpna 2009 | #

    [9] veena: Díky!

    Ad dvě mezery. Já to tak používám, a na blogu je aspoň vidět víc kódu. Jde fakt jen o zvyk, i Pythonu dělám jen dvě mezery :) Vlastně to tak mám globálně nastavený pro celý Visual Studio a Intype.

  11.  

    g

    23.09 - 15. srpna 2009 | #

    to by clovek blil.. viz priklad „Podmíněné přiřazení“, opravdu nevim proc reseni cislo jedna by melo byt vubec nejhorsi, co kdyz je potreba udelat vic akci nez jednu, potom je to jedine reseni, k reseni cislo 3, nejvetsi prasecina je spis tohle reseni, protoze nevyhodnoti to nahodou prazdny string nebo nulu stejne jako null? pokud ano, tak to vubec neni tataz situace jako predchozi dve reseni ktere testovaly null

  12.  

    Aleš Roubíček

    07.34 - 16. srpna 2009 | #

    [11] g: Páč jde o případ konstruktoru s nepoviným parametrem. Pokud je tam prázdný string nebo nula, je to stále fuk, protže parametr neni validní. A že bych chtěl ještě něco přidávat do kontraktu pro nepovinný parametr, to si nejsem jistý, proto třetí příklad.

    Zvracet laskavě choď někam jinam, kdo to tu má po tobě pak uklízet.

  13.  

    Schmidt

    09.55 - 15. února 2010 | #

    Dobrý den pane Roubíčku. Nemyslím si, obrazně řečeno, že když někdo bude umět mluvit méně plynule Anglicky než rodilý Angličan, tak by se měl na to mluvení vyprdnout a mluvit jen svou rodnou řečí. Chápu, že jste zřejmě někde jinde než já, ale to co tu uvádíte jsou opravdu extrémy a nevidím důvod proč za to ještě někoho trestat.

    Dělám si komentáře kde uznám za vhodné a je to pořád lepší než komentáře žádné. Používám syntaxi která je stejně platná jako ta vaše a výsledný kód bude stejně funkční. Při těch výkonech co dnes počítače mají, jim pár mili sekund zdržení při překladu, či vykonání kódu, nebude vadit. Možná, že výsledná assembly bude úplně stejná jako ta vaše, ten kompilátor si to přebere a vygeneruje stejný kód i bez toho buzerování a lpění na maličkostech.

    Myslím že v prvé řadě jde o to, zda kód dělá práci kterou dělat má. Pokud chcete mít kód od svých podřízených psaný podle vás, tak to piště rovnou celé sám, vždyť vy ty lidi nepotřebujete. Vůbec jím nedáváte svobodu jejich vlastního pohledu.

    Váš přístup bych nazval „Extrémní programování“ a jde spíše o určitou formu exhibicionismu.

    S přáním pěkného dne Schmidt

  14.  

    Aleš Roubíček

    13.31 - 15. února 2010 | #

    [13] Schmidt: To že kód dělá to, co má dělat, je základní předpoklad. Pokud chceme zdělit co kód dělá, musíme ho psát přehledně a čitelně. Čím více dodržujeme pravidla v týmu, tím je větší pravděpodobnost, že to po nás dokáže rozluštit i někdo jiný než my sami a kompilátor.

    Spousta kódu se mnohem vícekrát čte než píše, proto je nezbytné, aby byl čitelný a srozumitelný.

    Já vám neříkám, že nesmíte psát komentáře kam chcete. Já říkám, že komentáře velice rychle ztrácejí svou platnost a tudíž srozumitelnost kódu snižují. Proto je vhodné se jim ve většině případů vyhnout. Nebo zvolit lepší způsob jak myšlenku v něm ukrytou zachytit.

    Ano, způsob jakým se na vývoj software koukám, má blízko k extrémnímu programování. Ale to je z praxe vyvinutá a osvědčená metodika vývoje.

    Zkuste můj text nebrat jako osobní útok, ale jako námět k zamyšlení.

  15.  

    LiborBes

    00.22 - 3. května 2010 | #

    Zdravim, pekny blog!!!

    Jedna pripomienka co tu este neodznela:

    var user = new User { Name = „Josef Novák“, Gender = Gender.Male, };

    Aj ked rad tuto konstrukciu pouzivam – jej uplatnenie nemusi byt vzdy tuti-fruti. Jedna vec je citatelnost, ina napomocnost pri krokovani (vacinou pouzivane pri hladani chyb).

    Ak sa tento pristup pouzije napr. pri mapovani SOAP interface na interne struktury s vnorenymi triedami, debuger pri chybe (napr. neplatne nastavenie) sa zastavi na jednom (celom) riadku – nie vsak na konkretnej vlastnosti.

    Taktiez:

    Trieda trieda = new Trieda() { PodTriedaPrva = new PodTrieda() { Retazec = „1“ } PodTriedaDruha = new PodTrieda() { Retazec = „2“ } PodTriedaTretia = new PodTrieda() { Retazec = null } }

    nemusi byt najstasnejsim riesenim, ak sa potrebujem dokrokovat do konstruktora napr. „tretej“.

  16.  

    Aleš Roubíček

    07.19 - 3. května 2010 | #

    [15] LiborBes: Řekněme, že existují lepší způsoby ověřování funkčnosi kodu, než je debugování. ;) (Myslím tím unit testy.) I tak, nic vám přece nebrání před debugem použít refactoring pro „rozřádkování“ inicializátoru a po jeho skončení vrátit kód do čitelnější podoby pomocí refactoringu „Introduce initializer“.

Místo pro tvůj názor

Povinné je jméno a komentář, z e-mailu se rozpoznají Gravatary.
Komentář je formátován pomocí Texy! syntaxu.
Například: **tučný text**, *kurzíva*, "text odkazu":adresa.
Internetové adresy jsou převáděny na odkazy.
Na komentáře se můžete odkazovat pomocí [číslo komentáře].

Nový komentář