Chyby v návrhu: používání řetězců k reprezentaci složených dat

Od Petr Zemek, 2014-10-11

V dalším díle našeho seriálu o chybách v návrhu se podíváme na nešvar, kterého se mnohdy nevědomky dopouštíme: místo doménových typů k reprezentaci složených dat používáme řetězce.

Citát na začátek

The string is a stark data structure and everywhere it is passed there is much duplication of process. It is a perfect vehicle for hiding information.

-- Alan Perlis

(De)motivační příklad: reprezentace osob jako řetězec

Pojďme si ukázat typický příklad kódu, který využívá řetězce k reprezentaci složených dat. Kód bude v Pythonu, ale princip je na jazyce nezávislý. Mějme aplikaci, která potřebuje reprezentovat osoby. Tak si jich pár vytvoříme:

authors = ['Betty Elms', 'Diane Selwyn', 'Adam Kesher']

Dále mějme modul, který nám zajišťuje vytváření emailů. Jelikož chceme, aby emaily působily přívětivě, budeme autory oslovovat křestním jménem. Kód pro vytvoření oslovení pak může vypadat takto:

for author in authors:
    name = author.split(' ')[0]
    greetings = 'Dear ' + name + ','  # Dear Betty, ...

Jinde bychom zase potřebovali autory seřadit podle jejich příjmení. To lze při současné reprezentaci autorů udělat takto.

sorted_authors = sorted(authors, key=lambda author: author.split(' ')[1])
# ['Betty Elms', 'Adam Kesher', 'Diane Selwyn']

Možná už sami tušíte, že toto není ideální přístup. Schválně, zkuste se zamyslet, co za problémy nám současná reprezentace autorů přináší.

Co za problémy nám to přináší?

Použití řetězců k reprezentaci složených dat nám přináší následující problémy.

  • Duplikace kódu. Na každém místě, kde potřebujeme pracovat s "částí" autora, jej potřebujeme "rozbít". V kódu výše to odpovídá rozdělení autora podle mezery pomocí metody split().
  • Implicitní předpoklady. V kódu, který pracuje s autory, implicitně předpokládáme, že (1) každý autor je tvořen jménem a příjmením, (2) nejdříve je uvedeno jméno a pak příjmení a (3) jméno a příjmení je odděleno mezerou. Tyto předpoklady nejsou nikde explicitně uvedeny. A i kdyby byly uvedeny v komentáři, tak se na ně snadno zapomene, protože je nikdo nevynucuje.
  • Porušování principu "Tell, don't ask". O tomto principu jsem zde již psal. Co v kódu děláme, tak je, že si autora sami rozkouskujeme na části (vnitřnosti) a pak si s těmito částmi hrajeme sami. Princip "Tell, don't ask" nám říká, že bychom objektu měli říct, co od něj chceme, a on by nám to měl sám poskytnout. Tím, že si autora rozdělujeme sami, porušujeme zapouzdření.
  • Špatná rozšiřitelnost. Co kdybychom k osobám chtěli přidat i prostřední jméno? Nebo tituly. Naše současné řešení, které pracuje s osobou jako řetězcem, je v tomto ohledu velice špatně rozšiřitelné. Při změně formátu autorů musíme taktéž změnit všechen kód, který s nimi pracuje.

Jaká je vhodnější reprezentace?

Místo řetězce si vytvoříme doménový typ - osobu:

class Person:
    def __init__(self, name, surname):
        self.name = name
        self.surname = surname
 
    @property
    def full_name(self):
        # Dekorátor property nám umožní psát author.full_name bez nutnosti
        # použít závorky. Pro uživatele naší třídy je to přirozenější, než
        # psát author.full_name().
        return self.name + ' ' + self.surname
 
    def __str__(self):
        # print(author) vytiskne celé jméno
        return self.full_name

Náš původní kód pak bude vypadat následovně:

authors = [
    Person('Betty', 'Elms'),
    Person('Diane', 'Selwyn'),
    Person('Adam', 'Kesher')
]
 
for author in authors:
    greetings = 'Dear ' + author.name + ','
 
sorted_authors = sorted(authors, key=lambda author: author.surname)

Práce s osobou je tak soustředěna do třídy, zmíněné implicitní předpoklady mizí, nedochází k porušování "Tell, don't ask" a řešení je rozšiřitelné.

Tip na závěr

Vždy, když budete chtít použít řetězec, tak se nejdříve zamyslete, zda vaše data nejsou složená a zda se někde nepřistupuje k jednotlivým částem. Pokud ano, nepoužívejte raději řetězec, ale vytvořte si na jejich reprezentaci třídu. Později, až zjistíte, že řetězec nedostačuje, může být již pozdě na to dělat větší změny, protože by si vyžadovaly rozsáhlé úpravy na mnoha místech.

Poznámky pro Pythonisty

Pokud jste četli můj předchozí příspěvek o řazení v Pythonu, tak pro řazení objektů dle zvoleného atributu (či zvolených atributů) lze použít i následující konstrukci:

from operator import attrgetter
sorted_authors = sorted(authors, key=attrgetter('surname'))

Dále, místo skládání řetězců

greetings = 'Dear ' + author.name + ','

lze využít formátování:

greetings = 'Dear {},'.format(author.name)
Obsah tohoto pole je soukromý a nebude veřejně zobrazen.

Filtrované HTML (využíváno)

  • Povolené HTML značky: <a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <table>
  • Zvýraznění syntaxe kódu lze povolit přes následující značky: <code>, <blockcode>, <bash>, <c>, <cpp>, <haskell>, <html>, <java>, <javascript>, <latex>, <perl>, <php>, <python>, <ruby>, <rust>, <sql>, <text>, <vim>, <xml>, <yaml>.
  • Řádky a odstavce se zalomí automaticky.
  • Webové a e-mailové adresy jsou automaticky převedeny na odkazy.
CAPTCHA
1 + 8 =
Vyřešte tento jednoduchý matematický příklad a vložte výsledek. Např. pro 1+3 vložte 4.
Nějak se mi tady rozmohl spam, takže poprosím o ověření.

fadawar (neověřeno)

10 years 1 month zpět

Ďakujem za článok. Bolo to krátke, výstižné a užitočné. Dobrá práca!