Spustit prezentaci

Doporučené postupy v programování

Návrh procedur, funkcí a metod

Účel · Soudržnost · Délka a název · Práce s parametry · Vracení hodnot · Výjimky a chybové kódy

Lubomír Bulej

KDSS MFF UK

Základní terminologie

Rutina

Specifické druhy rutin

Primárním účelem funkcí je typicky spočítat nějakou hodnotu jako funkci vstupních parametrů. Opravdu čistých funkcí se v typickém programu asi najde málo, pokud to není program ve funkcionálním jazyce. Hlavní charakteristikou funkcí je absence vedlejších efektů, což má celou řadu výhod. Nicméně z pohledu běžného programátora se čistě funkcionální programování může jevit poměrně málo intuitivní.

Procedury jsou typické pro imperativní styl programování, kdy má programátor absolutní kontrolu nad průběhem výpočtu. Procedury obecně reprezentují aktivitu a výsledkem procedury není hodnota, ale např. přechod systému do jiného stavu. Přestože řada procedur vrací nějakou hodnotu a čistě syntakticky se jedná o funkce, svou povahou jsou to stále procedury.

Nakonec jsou metody, což jsou procedury a funkce svázané s daty.

Proč vytvářet procedury a funkce?

Proč vytvářet procedury a funkce?

Omezení složitosti

Zamezení duplikace

Zvyšování výkonnosti

V základních kurzech programování se člověk typicky dozví, že procedury a funkce slouží hlavně k zamezení duplikace kódu, cehož důsledek je především to, že se program lépe vyvíjí, ladí, dokumentuje a udržuje. Vedle syntaktických detailů jak používat parametry a lokální proměnné se toho člověk víc moc nedozví.

Přestože takové vysvětlení v každém bodě říká pravdu, zdaleka se nedotýká např. mnohem důležitějšího aspektu a tím je zvládání složitosti s využitím abstrakce a skrývání informací. Zamezení duplikace kódu samo o sobě typicky nepřináší všechny zmiňované výhody – ty právě plynou až z dobře strukturovaného programu, přimž vhodná struktura nevznikne sama od sebe pouhým naskládáním kódu do procedur a funkcí.

Často abstrahované operace

Sekvence

x = stack[topIndex];
topIndex--;
x = stackPop();

Práce s ukazateli

x = object_ptr->member;
object_ptr->member = y;
x = object_get_member(object_ptr);
object_set_member(object_ptr, x);

Přístup k atributům třídy

Příklad: jméno posledního uzlu

Nedostatečná abstrakce

if (node != null) {
  Node currentNode = node;
  while (currentNode.next != null) {
    currentnode = currentNode.next;
  }

  leafName = currentNode.name;

} else {
  leafName = '";
}       

Abstrakce skrývající mechanizmus

leafName = getLeafName (node);

Příklad: párovost operací zamykání

Součástí výkonného kódu


uintptr_t alloc_pages (...) {
  lock (pgalloc_lock);
  ...
  // do something
  ...
  if (status = FAILURE) {
    unlock (pgalloc_lock);
    return;
  }
  ...
  // do something else
  ...
  if (status = FAILURE) {
    return;
  }
  ...
  unlock (pgalloc_lock);
}         

V nadřazené funkci

static
uintptr_t __alloc_pages (...) {
  ...
  // do something
  ...
  if (status = FAILURE) {
    return NULL;
  }
  ...
  // do something else
  ...
}

uintptr_t alloc_pages (...) {
  uintptr_t result;

  lock (pgalloc_lock);
  result = __alloc_pages (...);
  unlock (pgalloc_lock);

  return result;
}         

Častou překážkou při vytváření procedur, funkcí a metod bývá pocit, že pro triviální operace nemá cenu vytvářet funkce, zvlášť pokud se neopakují.

Problém jednoduchých operací

V čem spočívá trivialita?

A jak je to s tím opakováním?

A co s režií na volání funkce?

Pokud se opakují, není co řešit. Pokud se neopakují, dostáváme se do šedé zóny – je nutné zvážit zda se opakovat v principu mohou a jak ovlivňují soudržnost té konkrétní metody (viz. funkční soudržnost později).

Režie na volání funkce se není třeba bát. Pokud je funkce jednoduchá a překladač rozumný, předá parametry funkce v registrech, případně funkci přeloží inline. Volání funkce je něco jiného než podmíněný skok, takže i když je volání nepřímé, překladač může adresu skoku spočítat ve vhodný okamžik, takže v okamžiku zpracování instrukce skoku už je jasné, kam se bude skákat.

U moderních procesorů je navíc rozumné počítat s tím, že se snaží rozumně vykonávat operace spojené s voláním virtuálních metod, které jsou tak typické pro dnešní software.

Příklad: unikátní identifikátor

Nedostatečná abstrakce

public IdentifiedObject() {
  static int nextId = 0;
  this.id = nextId++;
  /* ... */
}        

Abstrakce skrývající mechanizmus

private int getUniqueId() {
  static int nextId = 0;
  return nextId++;
}

public IdentifiedObject() {
  this.id = getUniqueId();
  /* ... */
}       

Příklad: převod jednotek

Nedostatečná abstrakce

points = deviceUnits * (POINTS_PER_INCH / getDeviceUnitsPerInch ());

Abstrakce skrývající mechanizmus

int deviceUnitsToPoints (int deviceUnits) {
  return deviceUnits * (POINTS_PER_INCH / getDeviceUnitsPerInch ());
}

...
points = deviceUnitsToPoints (deviceUnits);

Při změně implementace

int deviceUnitsToPoints (int deviceUnits) {
  if (getDeviceUnitsPerInch () > 0) {
    return deviceUnits * (POINTS_PER_INCH / getDeviceUnitsPerInch ());
  } else {
    return 0;
  }
}       

Jak nemá vypadat metoda?

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];
          }

Jak má tedy vypadat správná metoda?

Správná metoda vykazuje silnou soudržnost

Soudržnost/koheze (cohesion)

Silně soudržné metody vykazují méně chyb

Jak se pozná silná a slabá soudržnost?

Ideální forma soudržnosti

Méně ideální formy soudržnosti

Jak se pozná silná a slabá soudržnost?

Nevyhovující formy soudržnosti

Závislost na okolí a pořadí volání

Minimalizace závislosti na okolí

Minimalizace závislosti na pořadí volání

Příklad: explicitní závislost

Bez zjevné závislosti

computeMarketingExpense (marketingData)
computeSalesExpense (salesData)
computeTravelExpense (travelData)
computePersonnelExpense (personnelData)
displayExpenseSummary (
    marketingData, salesData, travelData, personnelData)

Se zjevnou závislostí

expenseData = initializeExpenseData (expenseData)
expenseData = computeMarketingExpense (expenseData)
expenseData = computeSalesExpense (expenseData)
expenseData = computeTravelExpense (expenseData)
expenseData = computePersonnelExpense (expenseData)
displayExpenseSummary (expenseData)

Příklad: komentovaná explicitní závislost

Se zjevnou závislostí a komentářem

//
// Compute expense data. Each of the routines accesses the member
// data expenseData. DisplayExpenseSummary should be called last
// because it depends on data calculated by other routines.
//
expenseData = initializeExpenseData (expenseData)
expenseData = computeMarketingExpense (expenseData)
expenseData = computeSalesExpense (expenseData)
expenseData = computeTravelExpense (expenseData)
expenseData = computePersonnelExpense (expenseData)
displayExpenseSummary (expenseData)
Komentář k závislosti upozorňuje na nutnost specifického uspořádání a na riziko špatného výsledku při jeho nedodržení, ale není z něj jasné, proč je závislost (obzvláště v tomto případě) vůbec nutná. Pokud takový návrh nejsme schopni zdůvodnit, je pravděpodobně lepší se mu vyhnout.

Příklad: komentovaná závislost

Bez zjevné závislosti s komentářem

//
// The following calls must be in correct order. Inside the
// taskReachedState method, the Task Manager may decide to
// close the context which contains this task by calling the
// HostRuntimeInterface.closeContext method.
//
// If the calls are not properly ordered, the Host Runtime will not
// have been notified about the task's completion (the notification
// happens in the notifyTaskFinished call) and will refuse to close
// the context (throwing IllegalArgumentException).
//
// This would lead to a race condition.
//
hostRuntime.notifyTaskFinished(TaskImplementation.this);
hostRuntime.getHostRuntimesPort().taskReachedState(
  taskDescriptor.getTaskTid(),
  taskDescriptor.getContextId(),
  processKilledFromOutside
    ? TaskState.ABORTED
    : TaskState.FINISHED
);
Příklad je převzat ze softwarového projektu Davida Majdy. Pro demonstrační účely není podstatné, co přesně dělají metody notifyTaskFinished a taskReachedState, ale to, jak podrobně je chování v komentáři popsáno.

Délka procedur a funkcí

Studie chybovosti v závislosti na délce metody

Délka procedur a funkcí

Rozumná délka

Názvy procedur a funkcí

Název by měl přesně a úplně popisovat, co procedura dělá nebo co funkce vrací.



Obecné požadavky

Souvislost názvu s návrhem procedur a funkcí

Tvorba názvu procedur a funkcí

Typický formát

Upřednostňujte krátká jména pokud to jde

Tvorba názvu procedur a funkcí

Použití správných antonym

  • add/remove
  • begin/end
  • create/destroy
  • first/last
  • get/put
  • get/set
  • increment/decrement
  • insert/delete
  • lock/unlock
  • min/max
  • next/previous (prev – symetrické)
  • old/new
  • open/close
  • show/hide
  • source/target
  • start/stop
  • up/down

Parametry procedur a funkcí

Význam parametrů

Druhy parametrů

  • role parametru
    • vstupní
    • modifikovatelné – vstupně/výstupní
    • výstupní
  • způsob předání
    • hodnotou
    • odkazem

Obvyklé korelace

Zásady pro práci s parametry

Vyhýbejte se modifikovatelným a výstupním parametrům

Snažte se použít mechanizmus návratové hodnoty

  • v ostatních jazycích (C/C++, Java, C#, ...) vrátit strukturu/objekt
    • speciálně při vracení více než 2 hodnot
  • Zásady pro práci s parametry

    Situace vhodné k použití modifikovatelných a výstupních parametrů

    Použití modifikovatelných a výstupních parametrů

    Zásady pro práci s parametry

    Podobné parametry uvádějte ve stejném pořadí u všech funkcí

    fprintf(stream, format,...)
    fputs(str, stream)
    strncpy(dst, src, len)
    memcpy(dst, src, len)

    Vyhněte se nepoužívaným parametrům

    Neměňte vstupní parametry v těle metody

    Klíčová slova const resp. final často znepřehledňují kód, a tak je málokdo používá. Při návrhu nových jazyků by možná stálo za úvahu dát parametrům sémantiku konstant automaticky, bez potřeby klíčového slova.

    Příklad: použití vstupních parametrů

    Vstupní parametr supluje pomocnou proměnnou

    float response (float inputSample) {
      sampleHistory.store (inputSample);
    
      inputSample = 0.0f;
      for (int i = 0; i < coefficients.length; i++) {
        ...
        inputSample += coefficients [i] * sampleHistory.previous (i);
        ...
      }
      ...
      return inputSample;
    }       

    Příklad: použití vstupních parametrů

    Vstupní parametry jsou tabu (pro modifikaci)

    float response (final float inputSample) {
      sampleHistory.store (inputSample);
    
      float outputValue = 0.0f;
      for (int i = 0; i < coefficients.length; i++) {
        ...
        outputValue += coefficients [i] * sampleHistory.previous (i);
        ...
      }
      ...
      return outputValue;
    }       

    Zásady pro práci s parametry

    Preferujte abstraktní typy

    Vyhněte se booleovským parametrům

    K interfacům a abstraktním typům: Viz Effective Java: Programming Language Guide, Item 34.

    K booleovským parametrům: Náhrada výčtovým typem/konstantou má i výhodu flexibility v případě, že časem přibudou další možné hodnoty parametru. To se u boolovských parametrů docela často stává, více viz výstižně nazvaný článek Booleans suck.

    Zásady pro práci s parametry

    Vyhněte se použití více než 5–7 parametrů

    Předávejte parametry odpovídající poskytované abstrakci

    Při zkracování seznamu parametrů funkce náhradou několika atributů jednoho objektu celým objektem je dobré se zamyslet, zda je fakt, že funkci chcete posílat celý objekt náhoda nebo systematická záležitost. Náhoda se pozná tak, že při volání funkce nemáte vždy dotyčný objekt "v ruce" – v tom případě je lepší nechat seznam parametrů být, protože budete muset u některých volání funkce objekt uměle vytvářet. Naopak, pokud zjistíte, že to náhoda není, stojí za to se chvilku zastavit nad strukturou kódu – můžete např. přijít na to, že daná funkce by měla být metoda objektu, který jí posíláte.

    Návratové hodnoty

    Procedura vs. funkce

    Návratové hodnoty

    Nepřetěžujte význam návratové hodnoty funkcí

    Vyhněte se používání procedur ve výrazech

    Příklad: testování výsledku procedury

    Nevhodné použití procedury ve výrazu

    if (report.formatOutput (formattedReport) == FormatResult.SUCCESS) {
      ...
    }       

    Zdůraznění procedurálního charakteru metody

    formatResult = report.formatOutput (formattedReport);
    if (formatResult == FormatResult.SUCCESS) {
      ...
    }       

    Znemožnění použití procedury ve výrazu (v Javě nešikovné)

    report.formatOutput (formattedReport, resultHolder);
    if (resultHolder.result == FormatResult.SUCCESS) {
      ...
    }       

    Obecná doporučení pro vracení hodnot

    Předčasný návrat používejte s rozvahou

    Používejte jednotný název pro proměnnou s výsledkem

    Vracejte prázdné kontejnery místo null

    K prázdným polím vs. null viz Effective Java: Programming Language Guide, Item 34.

    S podporou pro funkcionální programování přibyla v Javě od verze 8 třída Optional, což je (immutable) kontejner na 1 hodnotu.

    Předávání doplňkových informací

    Hlavní typy doplňkových informací

    Hlavní způsoby předávání doplňkových informací

    Příklad: výstupní stavová proměnná

    Obecná třída pro výstupní stavové proměnné

    public final class StatusHolder <S> {
      public S status;
    }       

    Použití s konkrétním typem stavové informace

    QueryResult DB.execute (
      Query query, StatusHolder <QueryStatus> statusHolder);
    ...
    StatusHolder <QueryStatus> queryStatusHolder =
      new StatusHolder <QueryStatus> ();
    QueryResult queryResult =
      db.execute (findInactiveUsersQuery, queryStatusHolder);
    
    if (queryStatusHolder.status == QueryStatus.SUCCESS) {
      ...
      for (Row row : queryResult.rows ()) {
        ...
      }
    }       

    Příklad: strukturované návratová hodnota

    Obecná třída pro (objektové) návratové hodnoty

    public final class StatusResult <S, R> {
      public final S status;
      public final R result;
    
      public StatusResult (S status, R result) {
        this.status = status;
        this.result = result;
      }
    }       

    Použití s konkrétním návratovým a stavovým typem

    StatusResult <QueryStatus, QueryResult> DB.execute (Query query);
    ...
    StatusResult <QueryStatus, QueryResult>
      queryStatusResult = db.execute (findInactiveUsersQuery);
    
    if (queryStatusResult.status == QueryStatus.SUCCESS) {
      ...
      for (Row row : queryStatusResult.result.rows ()) {
        ...
      }
    }       

    Příklad: výjimka

    Třída vyjímek pro zpracování dotazu

    public class QueryException extends ... {
      ...
    }       

    Odchycení výjimky při selhání

    QueryResult DB.execute (Query query) throws QueryException;
    ...
    try {
      QueryResult queryResult = db.execute (findInactiveUsersQuery);
      for (Row row : queryResult.rows ()) {
        ...
      }
      ...
    } catch (QueryException e) {
      ...
    }       

    Výjimky vs. chybové kódy

    Výjimky

    ... ale ...

    Výjimky vs. chybové kódy

    Chybové kódy

    ... ale ...

    Problematika výjimky vs. chybové kódy je složitější – my jsme se jí tu jen zlehka dotkli. Zájemcům o motivační diskuzi lze doporučit k přečtení následující články (v uvedeném pořadí):

    1. Joel Spolsky: Exceptions
    2. Ned Batchelder: Exceptions vs. Status Returns
    3. Joel Spolsky: DoSomething()
    4. Ned Batchelder: Exceptions in the Rainforest

    Ned Batchelder má hezký model toho, jak vypadá software v článku "Exceptions in the rainforest". Podle něj má software 3 vrstvy, shora C-B-A. Nejnižší (A = Adapting software beneath) přizpůsobuje jiný kód našim potřebám. Někdy jsou to low-level volání, pak se často používají chybové kódy, které je dobré převádět na výjimky.

    Prostřední vrstva (B = Building pieces of your system, někdy také Business logic :-) slouží k vytvoření částí, ze kterých se skládá náš svět. Tady je prostor pro problem-specific koncepty, algoritmy a datové struktury. V prostřední vrstvě chceme být maximálně produktivní a chceme tady mít dobře čitelný kód.

    Nejvyšší vrstva (C = Combining it all together) ví co se děje, takže typicky ví, co dělat s výjimkami. Výjimky neznamenají, že error handling bude najednou snadnější, ale znamenají, že chyby z vrstvy A se neztratí, a že nemusíme "špinit" vrstvu B tím, že bude předávat chyby výše, do vrstvy C.

    Typicky pak vrstva A vesměs vyhazuje výjimky, vrstva B také, ale méně, a vrstva C primarne chytá výjimky a potenciálně něco užitečného dělá.

    A protože software je fraktální, dá se tenhle model embeddovat do různých vrstvev.

    Výjimky vs. chybové kódy

    Kdy používat výjimky místo chybových kódů?

    Co se týče toho zda výjimky používat, pro nastartování diskuze postačí již dříve zmiňovaná debata Joel Spolsky vs. Ned Batchelder. K tomu je např. zajímavá polemika na obhajobu chybových kódů od Douga Rosse:

    Základní pravidlo asi jako obvykle zní – pokud použití výjimek zjednoduší a zpřehlední kód, asi není co řešit. Důležité je pak používat výjimky správně používat. Nakonec asi není až tak podstatné, zda se používá to či ono, ale zda se programátor vědomě a systematicky věnuje obsluze abnormálních stavů.

    Zásady pro práci s výjimkami

    Výjimky používejte jen ve výjimečných situacích

    Co jsou určitě chyby?

    K výjimečným situacím: Viz Effective Java: Programming Language Guide, Item 39: Use exceptions only for exceptional conditions. To znamená nepoužívat výjimky pro řízení toku programu, jako např. vynechání kontroly Iterator.hasNext() a čekání na to až Iterator.next() vyhodí výjimku.

    Příklad: průchod kolekcí pomocí iterátorů

    V čem je problém?

    ...
    try {
      while (true) {
        Employee employee = employeeIterator.next ();
        ...
      }
    catch (NoSuchElementException e) {
    }       

    Příklad: použití výjimek pro řízení toku

    Cyklus ukončen výjimkou

    ...
    try {
      while (true) {
        Employee employee = employeeIterator.next ();
        ...
      }
    catch (NoSuchElementException e) {
    }       

    Správné použití iterátoru

    ...
    while (employeeIterator.hasNext ()) {
      Employee employee = employeeIterator.next ();
      ...
    }       

    Jak poznat výjimečné případy?

    Specifikujte kontrakt metody

    Kam umístit specifikaci kontraktu?

    Viz Effective Java: Programming Language Guide, Item 23.

    Zásady pro práci s výjimkami

    Používejte výjimky na odpovídající úrovni abstrakce

    Ve výjimce detailně popište problém

    Vyhněte se prázdným catch blokům

    K úrovni abstrakce: Viz Effective Java: Programming Language Guide, Item 43: Throw exceptions appropriate to the abstraction. To znamená, že výjimky, které metoda vyvolává by měly být na úrovni abstrakce odpovídající tomu, co metoda dělé, ne jak to dělá.

    K detailnímu popisu problému: Viz Effective Java: Programming Language Guide, Item 45.

    K prázdným catch blokům: Viz Effective Java: Programming Language Guide, Item 47.

    Příklad: úroveň abstrakce výjimek

    Metoda deklaruje výjimku závislou na implementaci

    class Employee {
      ..
      public TexId getTaxId () throws IOException {
        ...
      }
      ...
    }       

    Zřetězení výjimek pro změnu úrovně abstrakce

    class Employee {
      ...
      public TexId getTaxId () throws EmployeeDataNotAvailableException {
        ...
        try {
          ...
        catch (IOException e) {
          throw new EmployeeDataNotAvailableException (e);
        }
      }
      ...
    }       

    Java a výjimky

    Hierarchie výjimek


    Jazyková podpora

    Druhy výjimek

    Kontrolované vs. nekontrolované výjimky

    Snaha rozlišit dva druhy výjimek

    Důsledky nadužívání/nevhodného používání kontrolovaných výjimek

    Kontrolované vs. nekontrolované výjimky

    Kdy používat kontrolované a nekontrolované výjimky?

    Mnohem těžší na rozhodnutí je dilema kolem kontrolovaných a nekontrolovaných výjimek. Řada expertů je dnes považuje za omyl a doporučují vyhazovat pouze nekontrolované výjimky a kontrolované převádět na nekontrolované. Viz např. Bruce Eckel:

    Celou debatu pak ale poměrně dobře shrnuje Brian Goetz na webu IBM developerWorks:

    A poměrně dobrý návod poskytuje také Barry Ruzek na serveru Dev2Dev (odkaz vede jinam, puvodní server je nedostupný):

    Mapování výjimek do Javy

    Hierarchie výjimek


    Eventuality a chyby

    eventualita (contingency)
    Výjimečná, ale předpokládaná situace, která se dá popsat v pojmech odpovídajících zamýšlenému účelu funkce, a která vyžaduje alternativní zpracování na úrovni volajícího. Volající je na takovou situaci připraven a má strategii pro jejich řešení.
    chyba/porucha (fault)
    Neplánovaná situace, která metodě znemožnila vykonat zamýšlenou funkci, a která nemůže být popsána bez znalosti implementace funkce.

    Jaké výjimky použít?


    Situace Eventualita/Contingecy Chyba/Fault
    Považováno za součást návrhu ošklivé překvapení
    Očekávaný výskyt předvídatelný, ale vzácný nikdy
    Koho to zajímá kód volající metodu lidi, kteří mají odstranit problém
    Příklad alternativní návratové hodnoty programové chyby, selhání hardware, konfigurační chyby, chybějící soubory, nedostupné servery
    Nejlepší mapování kontrolovaná výjimka nekontrolovaná výjimka

    Kontrolované vs. nekontrolované výjimky

    Kontrolované výjimky vyvolávejte v situacích, kdy je možné zotavení

    Nekontrolované výjimky vyvolávejte při programových chybách

    K náhradě kontrolovaných výjimek za nekontrolované: Dobrý příklad je metoda parsující text v nějakém jazyce. Text může v principu obsahovat chyby, a je žádoucí, aby metoda v tom případě vyhazovala výjimku. Pokud by tato výjimka byla kontrolovaná, musel by programátor výjimku odchytávat i v případě, že by si byl jist, že metodě předává syntakticky korektní text (např. automaticky generovaný). Lepší řešení je použít nekontrolovanou výjimku a přidat metodu, která bezchybnost textu otestuje a vrátí true nebo false.

    Uvedený postup ale nelze použít vždy. Např. pokud bychom chtěli před smazáním souboru zkontrolovat, zda tento soubor existuje, může nám ho mezi testem a smazáním někdo "smazat pod rukama". Je tedy potřeba mít zaručen exkluzivní přístup k datům.

    Ke kontrolovaným a nekontrolovaným výjimkám: Viz Effective Java: Programming Language Guide, Item 40: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors.

    K užívání kontrolovaných výjimek: Viz Effective Java: Programming Language Guide, Item 41: Avoid unnecessary use of checked exceptions.

    Osobně se přikláním spíše k názoru, že kontrolované výjimky mohou být užitečné primárně ve vlastním kódu, kde si tím mohu zajistit, že nezapomenu na obsluhu nějakého chybového stavu. Vně svého kódu bych kvůli minimalizaci obtěžování konzumentů mého kódu vyhazoval výjimky nekontrolované. Ty je pak nutné velmi dobře zdokumentovat. Pro situace, kdy je možné provést test před voláním výkonné metody (a kdy se situace po volání testu nemůže změnit), bych se snažil mít k výkonným metodám vždy testovací metody. Výkonné metody by pak mohly vyhazovat nekontrolované výjimky (bylo by chybou programátora, že nezkontroluje, zda může výkonnou metodu volat). Pokud není možné test a vykonání operace rozumně oddělit, volil bych spíše nekontrolovanou výjimku, abych nezatěžoval konzumenta. Dá se očekávat, že konzument nejspíš bude můj kód nějak adaptovat a kontrolované výjimky si může na vlastním území zavést sám.

    Kontrolované vs. nekontrolované výjimky

    Příklad: java.io.RandomAccessFile

    public int read (byte [] b, int off, int len)
      throws IOException

    Příklad: java.io.DataInputStream

    public final void readFully (byte[] b, int off, int len)
      throws EOFException, IOException
    public final double readDouble ()
      throws EOFException, IOException

    Kontrolované vs. nekontrolované výjimky

    Příklad: java.util.Arrays

    public static <T>
    int binarySearch (T [] a, T key, Comparator <? super T> c)

    Příklad: java.rmi.Naming

    public static Remote lookup (String name)
      throws NotBoundException, MalformedURLException, RemoteException

    Zde stojí za zmínku, že obvykle pokud něco hledáme, tak počítáme s možností, že to nenajdeme. Proč je to v případě RMI lookupu jiné? Stejně tak pokud víme, že zadané URL je správně, tak proč muset řešit checked exception?

    Další doporučení pro práci s výjimkami

    Nevyhazujte výjimky z konstruktorů/destruktorů

    Snažte se využívat standardní výjimky jazyka

    K standardním výjimkám: Viz Effective Java: Programming Language Guide, Item 42.