Přejít na obsah | Přejít k hlavnímu menu | Přejít k vyhledávání

edhouse-CookieGdpr-Policy-s
2183043
0
/cz/gdpr/
218650B6B

Zpět na Blog

Lessons learned SQA

Průšvihy z praxe: k čemu je dobrý úplný rozklad na třídy ekvivalence

17.9.2025 Juraj Danihel

Píše se rok 2001, sedím v budově č. 9 na hlavním kampusu Microsoftu v Redmondu a můj tým usilovně pracuje na verzi .NET Framework 1.1.

Přemýšlím, čím to je, že se nám zatím úplně nedaří dosáhnout toho, aby všechny třídy v části, které říkáme BCL – Base Class Libraries – fungovaly dostatečně robustně v globalizovaném prostředí všech možných kultur a jazykových mutací.

Můj vedoucí mi mírně podrážděný telefonuje, stěžuje si na nedostatečnou osvětu u programátorů a domlouvá nám schůzku u týmu, který řeší statickou analýzu kódu. Chceme tam přidat několik pravidel, abychom pomohli vývojářům zachytit běžné chyby, které se většinou projeví až potom, co si uživatel přepne jazykové nastavení ve Windows.

V hlavě mi mimovolně naskočí jedna z našich vlastních tříd: System.Globalization.RegionInfo. Máme tam konstruktor, který má na vstupu parametr typu string. Jeho hodnota může být buď jeden z předdefinovaných kódů v ISO 3166, nebo název kultury/jazyka. Instance daného typu pak slouží jako jakýsi poskytovatel informací o regionu, který je asociovaný s daným kódem nebo kulturou.

Ukázka RegionInfo
Ukázka RegionInfo
Nastavuji aktivní kulturu ve Windows na turečtinu a zkouším vytvořit RegionInfo pro kód „IT“ – Itálie. Tohle funguje. Zkouším to stejné pro „it“, v téhle chvíli by měl konstruktor jenom normalizovat kód na „IT“ (ToUpper) a vrátit to stejné. Konstruktor ale hlásí, že daný region neexistuje.

Naskakuje mi pot na čele. Tohle není dobré. Přemýšlím, jak se bude můj vedoucí tvářit až mu budu oznamovat, že ani naši vlastní programátoři nebyli zatím dostatečně zasaženi osvětou, kterou tak vášnivě pumpujeme do zbytku organizace. A že jako tester jsem to neobjevil dřív. A jak se s tím pak asi poperou ty miliony vývojářů, kteří budou na .NET stavět své aplikace?

Co se stalo?

Problém je v normalizaci vstupu pomocí metody ToUpper. Tahle metoda, stejně jako několik dalších ve třídě String, má dvě přetížení:

  1. Bez vstupních parametrů: vrátí kopii řetězce převedenou na velká písmena.
  2. S jedním parametrem typu CultureInfo: vrátí kopii řetězce převedenou na velká písmena pomocí pravidel pro velká písmena dané kultury/jazyka.

Je velká šance, že si jako vývojář v téhle chvíli řeknu, že operace normalizace kódu ISO 3166 nemá být závislá na konkrétním jazykovém nastavení a sáhnu po první verzi bez parametru CultureInfo. Co si ale nevšimnu: Ve skutečnosti tahle metoda používá jako výchozí kulturu pro převedení písmen CultureInfo.CurrentCulture. Ta většinou koresponduje s aktuálně vybraným jazykovým nastavením uživatele ve Windows.

Kde se pak tohle vysype, je v jazycích, které mají zvláštní formy jinak běžných písmen. Třeba turečtina má 4 varianty písmena „i“: i, İ, ı, I. Podle pravidel pro tenhle jazyk je pro malé „i“ forma velkého písmena „İ“ – velké I s tečkou.

V téhle chvíli už je asi jasné, co se stalo – “it” ToUpper() = “İT” – a to není validní kód regionu.

Proč to testy zachytily pozdě?

Rychlá odpověď: neúplný rozklad na třídy ekvivalence. Základní rozklad pro konstruktor RegionInfo(string name) by mohl vypadat třeba takhle:

Případ

Parametr name 

Očekávaný výsledek 

Nevalidní – null  null  ArgumentNullExpection 
Nevalidní – prázdný  “”  ArgumentException 
Nevalidní kód/kultura  “ABCD”  ArgumentException 
Nevalidní – neutrální kultura bez asociovaného regionu  “en”  ArgumentException 
Validní – specifická kultura  “en-US”  Ok 
Validní kód ISO 3166  “IT”  Ok 
Validní kód ISO 3166, normalizace  “it”  Ok 

Bohužel tento rozklad nepokrývá, že pod pokličkou probíhá normalizace s využitím metody ToUpper a že se používá přetížení které závisí na aktuálním jazyku ve Windows. Opravený rozklad:

Případ

Parametr name 

Očekávaný výsledek 

Current Culture 

Nevalidní – null  null  ArgumentNullExpection  Nezáleží 
Nevalidní – prázdný  “”  ArgumentException   
Nevalidní kód/kultura  “ABCD”  ArgumentException   
Nevalidní – neutrální kultura bez asociovaného regionu  “en”  ArgumentException   
Validní – specifická kultura  “en-US”  Ok   
Validní – specifická kultura  “it-it”  Ok  en-US 
Validní – specifická kultura  “it-it”  Ok  tr-TR 
Validní kód ISO 3166  “IT”  Ok   
Validní kód ISO 3166, normalizace  “it”  Ok  en-US 
Validní kód ISO 3166, normalizace  “it”  Ok  tr-TR 

Důsledky a oprava

Konstruktor třídy RegionInfo nebyl zdaleka jediným problémovým místem, bylo jich mnohem víc. Musely se přepsat všechny operace na řetězcích, kde se vysloveně nevyžadovalo uplatnit pravidla pro specifický jazyk tak, aby používaly tzv. invariantní kulturu. Tím se získá objekt, který je nezávislý na jazykové mutaci a zpracování hodnot typu String bude nezávislé na aktuálním jazykovém nastavení ve Windows. V případě konstruktoru RegionInfo to znamenalo použít metodu ToUpper(CultureInfo.InvariantCulture).

Ten problém byl ale ve skutečnosti ještě hlubší. Skutečným zdrojem byl nevhodný návrh operací ve třídě String, které jako výchozí nastavení pro většinu operací používají CurrentCulture. Tohle nebylo možné jednoduše opravit, protože třída Systém.String už byla venku mezi zákazníky a z důvodu kompatibility nebylo možné najednou změnit výchozí chování jejich metod.

V průběhu delšího období se tak postupně zavedlo několik nápravných opatření, aby se zamezilo nesprávnému použití:

  1. Pravidla pro statickou analýzu kódu, které upozorňovaly vývojáře na možné riziko.
  2. Přidání invariantních verzí některých metod do třídy String, např. ToUpperInvariant(). To mělo zvýšit pravděpodobnost, že vývojář, který chce zpracovat hodnoty způsobem nezávislým na aktuální jazykové mutaci, objeví a použije tuhle novou metodu. A ne zrádnou ToUpper s výchozím nastavením závislým na CurrentCulture.
  3. Sepsání dokumentace popisující doporučené použití a zpracování hodnot typu String v .NET.
     

Nic z toho už bohužel nemohlo zvrátit nešťastný původní návrh, jenom omezit negativní důsledky.
 

Tati, to už je dávná historie

Jako kdybych slyšel mé dospívající syny, když píšu tyhle řádky. .NET 1.1, to existovalo? Každé pozvání ke společnému zhlédnutí mého oblíbeného filmu začíná otázkou „A je to barevné nebo černobílé?“.

Současnost je rozhodně barevná. Ale jsou věci, které zůstávají černobílé: buď je ten rozklad úplný nebo není. A když není, máš problém.
 

 

Sdílet článek

Autor

Juraj Danihel

Juraj Danihel Test lead s vášní pro technické řešení problémů. Rád doprovázím testery na různých projektech, v posledních letech hlavně v doméně elektronové mikroskopie.

Edhouse newsletter

Získejte aktuální info ze světa Edhouse - novinky, setkávání, aktuální trendy softwarové i hardwarové.

Registrací vyjadřujete souhlas se zpracováním osobních údajů.

Děkujeme za váš zájem o odběr našeho newsletteru! Pro dokončení registrace je potřeba potvrdit vaše přihlášení. Na zadaný e-mail jsme vám právě zaslali potvrzovací odkaz. Klikněte prosím na tento odkaz, aby bylo vaše přihlášení dokončeno. Pokud e-mail nenajdete, zkontrolujte prosím složku nevyžádané pošty (spam) nebo složku hromadné pošty.