Squak Smalltalk [6 * 9 = 42] whileTrue: [World run]
Stopařův průvodce jedním převážně neškodným programovacím jazykem
smalltalk
Swiki
  • Domů
  • Zpět
  • Tento server

    Squeak
  • Úvod
  • Smalltalk
  • Články
  • Knihy
  • Dokumentace
  • FAQ
  • Tutoriály
  • Download
  • Odkazy

    Komunita
  • CSSUG
  • Oznámení
  • Projekty
  • O nás
  • Fórum
  • Kontakt

     

  • Nejčastější chyby ve Smalltalku

    (přeloženo/upraveno z originálu Classic Smalltalk Bugs by Ralph Johnson -- University of Illinois at Urbana-Champaign)

    Úvod: Každý programovací systém/jazyk je náchylný k dělání určitých nejčastějších chyb. Dobrý programátor se musí naučit se těmto chybám vyhýbat a Smalltalk není výjimkou.
    Přestože Smalltalk mnoho častých chyb eliminuje (např. lineární průchod polem nebo seznamem - stačí použít zprávu do:), tak zde nabízím seznam těch nejčastějších/začátečnických chyb.

    Důvody: Existuje hned několik pádných důvodů, proč se zabývat shromažďováním nejčastějších chyb.
    1. pomoc zkušenějším programátorům při testování, ladění a správném návrhu programů.
    2. snížení frekvence výskytu chyb u začínajících programátorů, kteří jich budou alespoň částečně ušetřeni.
    3. Znalost tohoto typu chyb může designerům Squeaku pomoci jej upravit, aby se snažil je eliminovat nebo psát různé automatické kontroly

    Poznámka překladatele:
    Původní dokument by upraven a doplněny přímo pro konkrétní implementaci Smalltalku jménem Squeak.
    PS: Jména tříd a metod budou zvýrazněny kurzívou. Je-li potřeba u zprávy uvést i třídu, která jí rozumí, tak se píše: Trida>>zprava.

    Bug 1: Třídy proměnné velikosti

    (Variable-sized classes)

    Set, Dictionary a OrderedCollection jsou třídy proměnné velikosti (mohou růst).
    Rostou tak, že vytvoří kopii sama sebe a přenesou se do této kopie (zpráva become: ).
    Pokud od některé této třídy dědíte do své vlastní nové podtřídy a přidáváte nějakou instanční proměnnou, tak si musíte dát velký pozor, aby se prováděla kopie i přidaných proměnných (např. právě při růstu), jinak může docházet k záhadné ztrátě dat (v nepředpovidaném místě výpočtu).

    Smalltalk-80 R4.0 (a asi i některé starší verze) měli metodu
    copyEmpty: v abstraktní třídě Collection, kterou bylo nutno předefinovat pokud jste odvozovali vlastní podtřídu (kde přibyly nějaké nové instanční proměnné), takže
    řešení je prosté: stačí napsat ve své třídě vlastní verzi metody copyEmpty:.

    Je dobré podotknout, že není problém vytvořit si pomůcku, která by kontrolovala při vytváření vlastní třídy, zda dědí z Collection (nebo některé její podtřídy, isKindOf:) a zda přidává nějaké nové instanční proměnné a v takovém případě automaticky vytvořit metodu copyEmpty: (dokonce i včetně jejího kódu).


    Práce s kolekcemi

    (Collection bugs)

    Bug 2: zpráva add: vrací svůj argument


    U každé rostoucí kolekce, zpráva add: vrací svůj argument a ne příjemce této zprávy, jak někteří mylně předpokládají.

    Tito pak chybně píšou
    (c add: x) add: y
    (výraz je syntakticky správně, ale sémanticky pouze pokud byste chtěli prvek y přidat do prvku x místo do kolekce c), když chtěli ve skutečnosti napsat
    c add: x; add: y
    nebo
    c add: x. c add: y
    .
    Poznamenejme, že toto je dobrý případ použití zprávy yourself, takže můžete napsat např.:
    (Set new 
    	add: x; 
    	add: y;
    	...;
    	yourself).

    Výsledkem tohoto výrazu je nová instance třídy Set s vloženými prvky x, y, ...

    Existuje dobrý důvod proč add: vrací svůj argument (a pokud byste se rozhodli jej implementovat tak, že by vracel příjemce, tak minimálně zmatete každého jiného smalltalk-programátora na světě).

    Metoda add: vracející svůj argument vám často ušetří nutnost využít dočasnou proměnnou. Vkládaný objekt můžete vytvářet přímo ve výrazu pro argument a případné dodatečné zprávy poslat až výsledku této zprávy add:.
    (c add: String new) zprávaProString: 'arg-něco'.


    A pokud chcete přistupovat ke kolekci, tak stačí použít zprávu yourself a nebo využít mechanismus kaskády zpráv (pomocí středníku) tak, jak bylo předvedeno výše.

    Bug 3: změna kolekce během interace nad ní samotnou

    (changing collection while iterating over it)

    Nikdy, nikdy a do třetice nikdy byste neměli iterovat (procházet v cyklu) přes kolekci, jež je iterační smyčkou nějak modifikována (pořádí či počet jejích prvků). Budou-li elementy kolekce odstraňovány během iterace, pak může dojít k vynechání nebo může být některý element vybrán/zpracován dvakrát. Místo toho udělejte nejprve kopii kolekce, kterou chcete procházet, pomocí metody copy, např.:
    aCollection copy do: [:each | aCollection remove: each].

    je správně, ale vypůstíme-li copy tak nikoliv.

    Mario Wolczko navrhl řešení odstraňující tento problém (ovšem na úkor výkonnosti). Je nutno změnit třídy pro kolekce. Každa metoda sloužící pro iteraci vloží kolekci do speciální množiny právě-iterovaných kolekcí (IteratedCollections), pak se provede vlastní blok iterace a nakonec se kolekce zase z této množiny odstraní.

    Kolekce jsou obvykle modifikovány užitím at:put: nebo basicAt:put:, takže se do nich musí přidat kontrola, zda se kolekce nevyskytuje v právě-iterovaných-kolekcích. Pokud je v takovém případě proveden pokus o modifikaci kolekce, tak je signalizována chyba. Buď můžete tuto techniku (kontrolu) používat vždy a nebo jen v ladící fázi vývoje vaší aplikace. Tyto změny tříd kolekcí (changes) jsou uloženy v souboru Iterator-check.st, který je k dispozici na ftp://ftp.sunet.se/pubd/lang/smalltalk/MANCHESTER/manchester/4.0/Iterator-check.st.

    Bug 4: změna kopií kolekcí

    (modifying copies of collections)
    (TODO: zlepšit překlad a srozumitelnost, přesně jsem totiž tento bug nepochopil)

    Pro objekt bývá běžné mít metodu pro přístup ke kolekci objektů, které lze modifikovat.
    Ovšem někdy objekt vrátí kopii této kolekce, aby zajistil, že originální kolekce nebude modifikována.
    Vy však namísto tohoto většinou předpokládáte, že tato zpráva změní původní kolekci.
    V tomto případě bývá hlavním problémem špatná dokumentace, a člověk, který by rád modifikoval původní kolekci se tak dostává do potíží.
    Například ScheduledControllers>>scheduledControllers.

    Řešení jsou možná dvě:
    1. Mít lepší dokumentaci a zajistit, aby nikdo nemohl modifikovat kopie kolekci vracené z takovýchto objektů.
    2. objekty, které si nepřejí aby jimi vracené kolekce mohly být modifikovány vrací takzvané neproměnlivé (immutable) verze těchto kolekcí, které se sami postarají o zahlášení chyby, pokud byl proveden pokus o jejich modifikaci.

    Bug 5: Chybějící ^


    Je velmi jednoduché ve výraze metody vynechat návratovou šipku.
    Pokud v metodě žádná návratová šipka není uvedena, tak Smalltalk automaticky vrací příjemce zprávy (tedy jako bychom tam napsali ^self).
    Takže k tomu, abychom spustili celý řetěz mystifikací, stačí pouze jedno opomenutí návratové šipky, které může způsobit logickou chybu, kdy si myslíme, že naše metoda bude vracet něco jiného než ve skutečnosti vracet bude.
    Pěkný příklad naleznete v bugu číslo 6.

    Bug 6: metody vytvářející instanci třídy

    (Class instance creation methods)

    Napsat správně metodu pro vytvoření nové instance je zjevně netriviální a vyžaduje dobré pochopení rozdílů mezi objektem reprezentující třídu a instancí třídy a také znalostí mechanismu posílání a zpracování zpráv.

    Správně je například:
    new
      ^super new initialize

    kde je třeba v každé třídě předefinovat metodu initialize sloužící pro kód inicializující počáteční hodnoty instančních proměnných.
    Zde je příklad instanční metody initialize:
    init
      super init "pro inicializaci zděděných instančních proměnných"
      "inicializace proměnných nově definovaných v této třídě"
    


    Nyní si ukažme několik příkladů špatně napsaného konstruktoru:
    Asi nejčastější chybou je zapomenutí návratové šipky (^)
    super new init

    To způsobí, že metoda místo vracení instance vrací třídu. Toto je speciální verze bugu číslo 5.

    Další chybou je vytvoření nekonečné smyčky například takovýmto zápisem:
    ^self new init

    ,kdy se nekonečně zanořuje volání zprávy new.

    Pokud v takovémto případě (či jakémkoliv jiném) Smalltalk neodpovídá (no response), tak můžete stisknout Alt-tečka (nebo Ctrl-tečka) a otevře se vám přerušení procesu (User Interrupt) a stisknutím tlačítka Debug otevřete okno debuggeru.
    Pokud debugger ukazuje na zásobníků několik zpráv new, pak jste pravděpodobně udělali tento typ chyby.

    Nakonec byste stejně měli definovat pouze jedno new pro každou hierarchii tříd a nechat potomky dědit tuto metodu a pro inicializaci využít předefinovávání metody initialize. Pokud budete new předefinovávat v každé třídě znova a znova, tak bude objekt při vytváření mnohokrát reinicializován, což určitě plýtvá časem a možná i pamětí!

    Jeden způsob řešení je vytvořit v třídě Object metodu new, která při inicializaci zavolá např. metodu init a metoda init v třídě Object by nedělala nic.
    Co ale v případě, pokud bychom potřebovali aby metoda init brala v úvahu i nějaké parametry? Právě z tohoto důvodu se toto řešení nepoužívá jako implicitní (tedy není obsaženo v třídě Object), ale je na každém programátorovi, aby o tomto způsobu věděl a patřičně jej přizpůsobil na svou hierarchii tříd, tak aby byl co nejefektivnější a návrhově čistý.

    Bug 7: přiřazení třídě

    (Assigning to classes)

    OrderedCollection := 2

    je naprosto legální výraz Smalltalku, ale má značně destruktivní účinky na vaši image!
    Toto může být omezeno, pokud bychom upravili kompilátor, aby v takovémto případě vracel varování o pokus přiřazení hodnoty globální proměnné obsahující třídu.

    Bug 8: zpráva become:


    become: je velmi mocná operace, ovšem může být také velmi nebezpečná a není vůbec těžké si s ní zničit vlastní image.
    Její hlavní použití je u rostoucích kolekcí (viz bug 1), protože umí každou referenci na starou verzi kolekce přepsat na reference na novou (větší) verzi té samé kolekce.
    Její syntaxe se mírně liší pro různé verze Smalltalku: Smalltalk/V a Smalltalk-80

    is a very powerful operation. It is easy to destroy your image
    with it. Its main use is in growing collections (see bug #1), since
    it can make every reference to the old version of a collection become a
    reference to the new, larger version. It has slightly different semantics
    in Smalltalk/V and Smalltalk-80, since "x becomes: y" causes every
    reference to x and y to be interchanged in Smalltalk-80, but does not
    change any of the references to y in Smalltalk/V.

    Suppose that you want to eliminate all references to an object x.
    Saying "x becomes: nil" works fine in Smalltalk/V, but will cause
    every reference to nil to become a reference to x in Smalltalk-80.
    This is a sure calamity. You want x to become a new object with no
    references, such as in "x becomes: String new".

    Bug 9: Recompiling bugs in Smalltalk/V


    It is easy to have references to obsolete objects in Smalltalk/V
    if you change code without cleaning things up carefully. For example,
    the associations whose keys are the referenced names in the Pool
    Dictionary are stored in the CompiledMethods at compile time. If you
    create a new version of the Pool Dictionary and install it by simple
    assignment then the compiled methods still refer to the old associations.

    If you substitute a new instance of Dictionary or replace, rather than
    update an association in a pool dictionary, you have to recompile all
    methods using variables scoped to that Pool.

    This is is also annoying when using ENVY, where the methods are under
    strict control. Perhaps Pool Dictionaries should be be first-class
    versioned pre-requisites of Classes, just like the class definition.

    BTW we are using/VPM 1.4 with ENVY 1.3

    1. If you prune & graft a subtree of your class structure you have to
    make sure that all referencing methods are recompiled. Otherwise you
    will run (or your customer, because this is only detected at run time)
    into an Deleted class error message.
    Thomas Muhr posted a "Bite" a while ago to handle this problem for
    Smalltalk/V 286.

    Bug 10: Otvírání oken

    (Opening windows)
    (Pozn.překladatele: Tento problém již není ve Squeaku aktuální)

    Smalltalk-V a starší verze Smalltalk-80 neprovádí návrat odesilatele (sender) po otevření okna (řízení programu není vráceno do bloku, kde bylo otevření okna vyvoláno). Takže kód po otevření okna nebude nikdy vykonán.
    Tato odchylka způsobuje mnoho frustrujících chyb. Například pokud se pokusíte otevřít 2 okna zároveň:
    "pro Smalltalk-V:" 	TextPane new open. TextPane new open. 

    "nebo pro Smalltalk-80:" aScheduledWindow1 open. aScheduledWindow2 open. 

    , tak obdržíme pouze 1 otevřené okno a část kódu bude zapomenuta.
    Tento problém byl opraven v Objectworks/Smalltalk R 4.1, takže tam předchozí kód vytvořil opravdu 2 okna, jak bychom očekávali.

    Oprava pro dřívější verze Smalltalk-80 je použití metody openNoTerminate pro otevírání oken, která oknu nepředává řízení hned po jejím vytvoření.
    Šikovným trikem je uložit si nové okno v globální proměnné, kterou můžete otestovat.

    Aad Nales říká, že oprava pro Smalltalk/V je použít rozvětvení procesů (fork) při vytváření nového okna:

    [Textpane open] fork.


    Pokud programátor není spokojen s tímto způsobem, tak mu nezbývá nic jiného než změnit kód pro "dispatcher" a odstranit zprávu dropSenderChain, která je hlavní přičinou těchto problémů.

    Bug 11: Bloky

    (blocks)
    (Pozn.překladatele: Tento problém již není ve Squeaku aktuální a týká se spíše starších implementací Smalltalku)

    Blocks are very powerful, and it isn't hard for programmers to get
    into trouble trying to be too tricky. To compound problems, the
    two versions of Smalltalk have slightly different semantics for
    blocks, and one of them often leads to problems.

    Originally blocks did not have truly local variables. The block
    parameters were really local variables in the enclosing method.
    Thus,

    x y
    x := 0.
    (1 to: 100) do: [:z | x := x + z]

    actually had three temporaries, x, y, and z. This leads to bugs
    like the following

    someMethod

    | a b |
    a := #(4 3 2 1).
    b := SortedCollection sortBlock: [:a :b | a someOperation: b].
    b addAll: a.
    Transcript show: a.

    When elements are added to b, the sortBlock is used to tell where
    to put them, but this will change a and b. addAll: is OK, but
    the "a" that gets displayed on the transcript will be an integer,
    not an array.

    Early versions of Smalltalk-80 (2.4 and before?) implemented blocks
    like this, and Smalltalk/V still does. However, in current PPS
    implementations, blocks are close to being closures. You can declare
    variables local to a block, and the names of the block parameters are
    local to the block. Most people agree that this is a much better
    definition of blocks than the original one. Nevertheless, people
    planning to use Smalltalk/V should realise that it has a different
    semantics for blocks.

    This difference can lead to some amusing problems. For example, here
    is some code written by someone who had obviously learned Scheme.

    anotherArray aBlockArray

    aBlockArray := Array new: 4.
    anotherArray := #(1 2 4 8).

    1 to: 4 do: [ :anIndex |
    aBlockArray at: anIndex put: [ (anotherArray at: anIndex) 2 ]].


    The programmer expected that each block would be stored in the array
    along with its own value of anIndex. If anIndex were just a local
    variable of the method then this will not work. It assumes that
    each execution of the block gets its own version of anIndex, and
    Smalltalk/V and old Smalltalk-80 actually make each execution share
    the same version.

    So, if you are using Smalltalk/V then be careful not to reuse the
    names of arguments of blocks unless you know that the blocks are
    not going to have their lives overlap. Thus,

    aCollect do: [:i | ...].
    bCollect do: [:i | ...].

    is probably OK because do: does not store its argument, so the
    blocks will be garbage by the time the method is finished.
    However, if the first block were stored in a variable somewhere
    and evaluated during the execution of the second block then
    problems would probably occur.

    Bug 12: Cached Menus


    Menus are often defined in a class method, where they are created
    and stored in a class variable or a class instance variable. The
    method will look something like this

    initializeMenu
    ...

    Note that accepting the method does
    not: 
    change the menu. You
    have to execute the method to change the class variable or class
    instance variable. Often the initializeMenu method is invoked by
    the class method initialize. This can lead to the strange effect
    that you can initialize the menu by deleting the class and filing
    it in again, but otherwise you don't seem to be able to change
    the menu (because you haven't figured out that you should really
    be executing the initializeMenu method).

    To make matters worse, it is possible that each instance of the
    controller, or model, or whatever has the menu, stores its own
    copy of the menu in an instance variable. If that is the case then
    it is not enough to execute initializeMenu, you also must cause each
    object to reinitialize its own copy of the menu. It is often easier
    to just delete the objects and recreate them.

    Often a class will have a #flushMenus method to clear out all menus.
    Typically the method that fetches the menu will check to see if it
    is nil and invoke initializeMenu if it is. So, flushMenus will just
    nil out the variable holding the menu. The best way to figure out
    what is happening is to look at all uses of the variable. Smalltalk
    experts rarely have problems with this bug, but it often confuses
    novices.

    Caching is a very common technique in Smalltalk for making programs
    more efficient in both time and space. Caching of menus is one of
    the simplest uses of caches, and other uses can create more subtle bugs.

    Bug 13: Smalltalk/V class library

    Thomas Muhr makes these comments about bugs in the Smalltalk/V
    class library that you should know if you want to keep your
    programs fast and correct.

    2. Never use symbols to label objects if you are dealing with many
    objects. This will slow down your system to an almost dead halt. Use
    strings instead.
    3. Never use Sets when you can otherwise assure the uniqueness. Look
    at the implementation of "add:" for Sets and you'll know what I mean:
    on every "add:" the new element is compared to all others resulting
    into a nonlinear time for adding to Sets.
    4. Do not think that if you "collect:" something from a
    SortedCollection, that your result will be sorted as the origin,
    unless you use the default sortBlock. This is one of the bugs provided
    by the language vendor

    Bug 14: Nesprávné použití kaskádové/kaskádních metod

    (Using cascaded messages improperly)

    (autor: jaeck@alc.com (William A. Jaeck)

    I had created a subclass of OrderedCollection with an instance
    method called with:. This is supposed to do the same thing as
    add:.

    Then, I implemented a class method called with:with: as

    with: arg1 with: arg2

    ^ self with: arg1; with: arg2

    This ended up producing a result as if I had implemented it as

    ^ self with: arg2

    The correct implementation of with:with: is, of course

    ^ (self with: arg1) with: arg2


    Bug 15: Používání třídních metod pro implementaci singulárního objektu (existuje vždy pouze 1 instance)

    (Using class methods to implement a singleton object)
    From: riks@ogicse.cse.ogi.edu (Rik Fischer SmOOdy)

    A common "dirty trick" is to use a class with class methods as an
    implementation for a "globally known" object which is expected
    to be unique. This class is never expected to be instantiated:
    instances would have no capabilities except those inherited by
    default from #Object.
    The trick usually works, but it qualifies as design by accident.
    A tyro might study the object as an example and be misled.
    (It is important to encourage re-use to keep the visible structure
    as comprehensible as we can)
    Future engineers might decide that there needs to be another "instance"
    of this globally known object. I have seen an example (identity
    of perpetrators withheld to protect the unconvicted) where the
    second instance was implmented as a sub-class, with numerous methods
    over-written to access a different class variable.

    On Smalltalk V/MAC, a quick scan found (Color MTrap Compiler and LCompiler )
    Smalltalk80 V4 comes with DefineOpcodePool but that may have other
    redeeming social value.
    The most egregious examples have come with other installed packages.

    Mr. Manners instead approves of the way Smalltalk80 uses the globally
    published objects named Transcript, Undeclared, and even Smalltalk itself.

    Bug 16: Předefinování operátoru = ale ne operátoru hash

    (Redefining = but NOT hash)
    From: Mario.Winter@FernUni-Hagen.de (Mario Winter)

    Kent Beck makes these comments about redefining = most often demands
    redefining hash, because all subclasses of Set only use hash to compare
    their Elements. So don`t wonder if your set contains elements which you
    consider to be equal, if you have not redefined hash to compute the same
    hash-value for ,equal" elements. Always remember that Object defines = as
    =, that is equality is defined as identity.


    Poděkování: Velké díky patří mnoha lidem, kteří se na tomto dokumentu podíleli:
    • muhr@opal.cs.tu-berlin.de (Thomas Muhr)
    • steinman@is.morgan.com (Jan Steinman)
    • knight@mrco.carleton.ca (Alan Knight)
    • mario@cs.man.ac.uk (Mario Wolczko)
    • peterg@netcom.com (Peter Goodall)
    • nales@cs.few.eur.nl (Aad Nales)
    • scrl@otter.hpl.hp.com (Simon Lewis)
    • msmith@volcano.ma30.bull.com (Mike Smith)
    • dai@mrco.carleton.ca (Naci Dai)
    • dcr0@speedy.enet.dec.com (Dave Robbins)
    • randy@tigercat.den.mmc.com (Randy Stafford)
    • Hubert.Baumeister@mpi-sb.mpg.de (Hubert Baumeister)
    • eliot@dcs.qmw.ac.uk (Eliot Miranda)
    • dmm@aristotle.ils.nwu.edu (donald)
    • amir@is.morgan.com (Amir Bakhtiar)
    • Piersol@Apple.com (Kurt Piersol)
    • sullivan@ticipa.ti.com (Michael Sullivan)
    • terry@zoe.network23.com (Terry)
    • brent@uwovax.uwo.ca (Brent Sterner)
    • frerk@informatik.uni-kl.de
    • nicted@toz.buffalo.ny.us (Nicole Tedesco)
    • riks@ogicse.ogi.edu (Rik Fischer Smoody)
    • marten@feki.toppoint.de (Marten Feldtmann)
    • mst@vexpert.dbai.tuwien.ac.at (Markus Stumptner)
    • Mario.Winter@FernUni-Hagen.de (Mario Winter)
    • jaeck@alc.com (William A. Jaeck)


    Originální zdroj: ftp://st.cs.uiuc.edu/pub/Smalltalk/st-docs/classic-bugs.txt

    Odkaz na tuto stránku

    • Články, poslední úprava dne 17 Únor 2010 v 11:18:52 uživatelem localhost





    Administrátoři: Pavel Křivánek, Zbyněk Křivka