Diagrames de seqüència UML: Errors més freqüents

Els diagrames de seqüència UML permeten modelar la interacció entre diversos participants que intercanvien missatges per a dur a terme una certa tasca. Un dels usos més habituals d’aquests diagrames és mostrar un escenari de funcionament d’un programari orientat a objectes; en aquest cas, es mostra com un conjunt d’instàncies de programari (objectes del llenguatge de programació triat) interactuen quan una d’elles rep una invocació d’una operació.

Aquest article documenta una sèrie d’errors freqüents que he detectat que es solen produir en els diagrames de seqüència dels dissenyadors novells. No es tracta tant de documentar errades de disseny com d’explicar les errades de llenguatge UML a l’hora d’expressar un disseny mitjançant un diagrama de seqüències.

Documents illegibles

Un diagrama de seqüències és un document que una persona ha de poder llegir en paper o en pantalla. Encara que sembli absurd, molts dissenyadors fan diagrames tant grans que, mostrats sencers en una pantalla o impresos en una pàgina de paper són impossibles de llegir. La situació és especialment greu quan ni tant sols en format digital i fent zoom es pot llegir el contingut del diagrama.

Exemple:

Diagrama illegible

Les causes d’això poden ser moltes no relacionades amb UML:

  • Format d'imatge incorrecte: S'ha fet servir un format amb pèrdua de qualitat que ha introduït massa soroll per a que es pugui llegir el diagrama
  • Nivell de zoom massa allunyat: El nivell de zoom del diagrama no permet llegir-lo sobre paper o sobre pantalla quan es mostra tot el diagrama a la pantalla
  • ...
Des del punt de vista UML, però, hi ha solucions aplicables per a simplificar diagrames. Qualsevol operació es pot deixar, en un diagrama de seqüències, sense documentar. Aleshores cal, però, fer un altre diagrama de seqüències on es mostri només el disseny d’aquella operació. Vegeu l’apartat “Operacions no dissenyades” per a un exemple d’operació documentada en un diagrama a part.

Errades en la representació de participants

Els participants, en un diagrama de seqüències del disseny, són objectes i classes de programari. Un participant que representi una classe es pot fer servir per mostrar la invocació d’operacions amb àmbit de classe (estàtiques).

Instàncies

Un participant es representa amb una etiqueta:

  • El participant representa una classe. Ex: Controller
  • El participant representa un objecte anònim. Ex: :Person
  • El participant representa un objecte amb nom. Ex: d:Department
Un objecte anònim és un objecte al que no donem nom al diagrama de seqüència. Això fa que no poguem identificar d’on surt aquest objecte. Per tant sol ser recomenable posar nom a tots els objectes que apareixen al diagrama. Com a excepcions a aquesta norma hi ha:
  • Objecte inicial del diagrama: Un diagrama de seqüències documenta el comportament d'un conjunt d'objectes a partir d'un objecte inicial del que documentem una operació. A aquest objecte inicial pot ser innecessari donar-li un nom.
  • Objectes singleton: De les classes singleton només n'hi ha una instància. Per tant, no cal que aquesta instància tingui nom.

Errades a les activacions

L’activació és la banda que es dibuixa damunt la línia de vida d’una instància quan aquesta rep la invocació d’una operació (o, en general, qualsevol missatge). Aquesta banda representa el temps que l’objecte està actiu ja sigui perquè està fent alguna mena de càlculs o perquè està esperant una resposta d’una invocació que, al seu torn, ha fet sobre un altre objecte.

Més d'un missatge cap a la mateixa activació

Dos missatges cap a la mateixa activació

Activació espontània d'una instància

Activació espontània d'una instància

En aquest exemple el missatge 2 s’origina des de la instància p:Persona sense que aquesta estigués prèviament activa. La instància, per tant, s’ha activat espontàniament. Si estem modelant un programari OO tradicional (sense objectes actius) això no pot passar mai, ja que qualsevol instància - i p:Person en particular - només pot actuar com a resposta a una invocació.

No mostrar quina operació inicial es documenta

Un diagrama de seqüències de disseny documenta com interactuen un conjunt d’objectes quan un d’ells (o una classe) rep la invocació d’una operació. Cal mostrar, però, quina és l’operació que s’està documentant.

Diagrama de seqüències d'una operació desconeguda

Aquest diagrama de seqüències documenta una operació de la classe Controller que fa dues invocacions sobre una instància p:Person. Però no sabem quina és l’operació de Controller que estem documentant.

Diagrama de seqüències de Controller::getPersonData

En aquest diagrama podem veure que s’està documentant l’operació Controller::getPersonData.

Nota: En aquest article, la majoria de figures mostren fragments de diagrames de seqüència. Per tant, no ens hem preocupat de mostrar en tots els casos quina operació s’està documentant.

Errades d'instanciació d'objectes

Missatge de creació mal representat

Missatge de creació mal representat

El missatge 1 d’aquest exemple pretén representar la creació de la instància p:Person, però en realitat mostra la invocació d’una operació create() sobre una instància p de tipus Person que existeix prèviament. El missatge 2, en canvi, mostra la forma correcta de representar la instanciació d’una instància.

Instàncies inexistents

En un diagrama de seqüències del disseny, tota instància que aparegui ha d’estar clar d’on surt. Si estem dissenyant per a un llenguatge de programació orientat a objectes típic una instància només podrà invocar operacions en algun determinats escenaris escenaris.

Suposem que estem documentant la invocació d’una operació op1() sobre una instància a de tipus A. La instància a només podrà invocar operacions:

  • Amb àmbit de classe (estàtica) sobre qualsevol classe visible per A
  • Amb àmbit d'instància (no estàtica) sobre una instància b:B quan:
    • La classe A té un atribut b de tipus B i la instància a té un objecte en aquest atribut (i no un null)
    • L'operació A::op1 té un paràmetre b de tipus B i la invocació ha rebut un argument no null en aquest paràmetre
    • La instància b de tipus B ha estat creada des de la mateixa operació op1() abans d'invocar-hi l'operació. Aquest cas no és gaire freqüent.
Ús d'una instància inexistent

En aquest exemple :Controller fa servir una instància :Person que no se sap d’on surt. A menys que la classe Controller tingui un atribut de tipus Person (i, en tal cas, caldria indicar com a nom de la instància :Person el nom de l’atribut), la instància :Controller no pot enviar missatges a :Person.

Operació getName invocada sobre una instància p:Person identificada correctament

En el segon exemple s’ha corregit l’error: :Controller fa servir una instància anònima de Dictionary

(que probablement ha rebut per injecció de dependències) i obté, a partir de l’identificador d’una persona, una instància p:Person. És sobre aquesta instància sobre la que invoca getName().

Instàncies anònimes

En alguns casos particulars, no mostrem d’on surt una instància quan està molt clar a quina instància ens referim i quin és el seu origen. És el cas d’instàncies de classes Singleton per a les que no volem mostrar el funcionament del patró Singleton o el cas d’instàncies de serveis que s’han injectat a la instància que fa la crida per injecció de dependències. Per a que quedi clar aquest ús, és recomenable no donar nom a aquestes instàncies.

En el cas dels diagrames de seqüència de la Capa de Domini, per exemple, recomano reservar aquest tractament per als controladors de la Capa de Gestió de Dades (o per als Diccionaris si encara no s’han introduït aquests controladors).

Cas general: Variables inexistents

El cas de les instàncies inexistents es pot extendre a qualsevol variable utilitzada en el diagrama de seqüències:
  • Nom d'instància
  • Variables fetes servir en arguments a la invocació d'operacions
  • Variables fetes servir en condicions de marcs OPT, LOOP i ALT

Operacions no dissenyades

En un diagrama de seqüències del disseny, habitualment, ens cal mostrar tot el disseny de totes les operacions que apareixen al diagrama. Qualsevol operació que sigui invocada ha de ser dissenyada, al seu torn, com a part de mateix diagrama de seqüències o en un diagrama de seqüències a part.

Les úniques operacions per a les quals no apareixerà cap disseny, per tant, són aquelles que una instància atén sense delegar en cap altra instància; és a dir, aquelles que la instància resoldrà sense invocar cap operació sobre cap altra instància:

  • Operacions consultores (getter) que només retornen el valor d'un atribut
  • Operacions modificadores d'atribut (setter) que només canvien el valor d'un atribut
  • Operacions que efectuen algun tipus de càlcul només amb valors (per oposició a objectes) que siguin atributs o arguments de la crida: Manipulació de números, strings, dates, etc.
Operació Company::getHighestPayedEmployee no dissenyada

En aquest exemple, l’operació getHighestPayedEmployee, suposant que no es tracti només d’una consulta d’un atribut, necessitarà recórrer els empleats associats a la companyia i trobar quin és el que està millor pagat. Per a fer-ho, haurà d’invocar operacions sobre instàncies de classe Employee. En aquest diagrama no es mostra el disseny d’aquesta operació.

Això no seria un problema sempre que en un diagrama a part es mostri el disseny d’aquesta operació:

Company::getHighestPayedEmployee

Notes:

  • En aquest exemple s'han fet seriv comentaris al costat de l'activació per explicar aquelles parts del disseny que no impliquen la interacció amb altres objectes. Per exemple, com s'inicialitza i modifica la variable maxSalary.
  • En aquest exemple s'ha fet servir un LOOP amb condició "for each emp in employees" enlloc de mostrar l'ús a baix detall d'un iterador.
  • Fixeu-vos que emp:Employee és una instància correctament identificada, ja que correspon a l'empleat actual de la iteració (apareix explícitament a la condició del LOOP). Pel que fa a employees hem de suposar que es tracta d'un atribut de Company (o una associació navegable des de Company cap a Employee amb nom de rol employees al costat d'Employee).

Errades de disseny

Finalment, moltes de les errades dels diagrames de seqüència són errades de disseny. És a dir, el diagrama de seqüències no només ha de tenir sentit i documentar correctament un disseny possible, sinó que, a més, ha d’estar correctament dissenyat.

Malgrat que no és la intenció d’aquest article parlar d’aquest tipus d’errors (que no són específics dels diagrames de seqüència, sinó que també es poden trobar en un disseny expressat directament en codi, per exemple), dóno aquí una llista dels que són més habituals:

  • L'operació no compleix amb el contracte (ja sigui un contracte explícitament documentat o un contracte implícit) i, per tant, no fa el que ha de fer.
  • No s'ha aplicat correctament els patrons Expert i Creador de Larman i, per tant, la cohesió i acoblament de la solució obtinguda són dolents.
  • Els noms donats a les operacions no són indicatius de la semàntica de les mateixes. Exemple: Una operació getEdat que, en realitat, modifiqui l'atribut dataNaixement de la instància sobre la que s'invoca.
  • No s'honora el principi de No Repetició de Hunt i Thomas i, per tant, hi ha dues o més operacions que tenen total o parcialment el mateix disseny. És especialment curiós quan es fa servir una operació ja dissenyada en un altre diagrama de seqüències tal qual i, malgrat tot, es torna a mostrar tot el disseny intern de l'operació en qüestió.

Referències

  • [LARMAN] Larman, Craig (2002) Applying UML and Patterns. Prentice Hall.
  • [PRAGPROG] Andy Hunt, David Thomas (2000) The Pragmatic Programmer - from journeyman to master. Addison Wesley
  • [UPC-ES2] Franch, Xavier (professor responsable) Assignatura ES2 de la secció de SI, departament LSI, UPC.
  • [UOC-EPOO] Jordi Fernández Gonzalez, Jordi Pradel i Miquel i Jose Antonio Raya Martos (coordinadors Jordi Cabot i Isabel Guitart). (2005) Enginyeria del programari orientat a l’objecte. Barcelona: UOC

blog comments powered by Disqus