Prof. Jirka Dell'Oro-Friedl
Hochschule Furtwangen University
Digital Media Designers need a precise and international language to create ideas and convey their designs to a team of developers. The Unified Modelling Language (UML) fulfills this requirement not only in the realm of software development, but for arbitrary complex systems. Its development started in the 1990s and is still going on. It is standardized as ISO/IEC 19505. UML makes systems, algorithms and data structures visible and tangible, thus enabling the designers and their teams to discuss, improve and produce them not only in early stages of development, but throughout the process, and, done well, even produces in large parts the final documentation.
This little booklet displays only three of the many types of diagrams UML 2.5 defines:
- Use Case Diagram
- Activity Diagram
- Class Diagram
and of those only a subset of the features. This resembles the minimum expertise a Digial Media Designer needs to master and should be sufficient to design systems of non trivial complexity.
A use case diagram modells a system on a very high level of abstraction from the perspective of the user. The system is modelled as a black box, offering interactions to the user. There may be various user roles using different interactions the system offers.
User roles may be extended to access more interactions, e.g. Power-User extends User. Use cases may include internal use cases that can also be depicted in the diagram and connected by a dashed line. Notes can further specify conditions or additional information.
Make sure not to go too much into details in the use case diagram. Stay on a level that provides a good overview!
This type of diagram is arguably the most versatile of the UML diagrams. An activity diagram models the behaviour of a system. While natural language is only suited to describe simple processes, using the graphical language in two dimensions and nesting (sub-activities) enables the designer to describe arbitrary complexity while maintaining readability and comprehensibility. In this chapter, designs displayed are on a very low level, so that they can be translated into very low level code. This is for explanation only and in real life, the designer will not go down into that much detail. The same structures apply though when working with activities that nest atomic actions. By nesting activities in again larger activities and so forth, the designer works on different levels of abstraction.
However, in the design process, the designer starts with the activities definied in the use case diagram, splits them up in smaller ones, and those again in even smaller, until all activities designed are trivial and the initial problem is solved.
Linear |
---|
console.log("Hello"); |
Conditional |
---|
...
if (!(x > 1))
console.log("Hello"); |
Exclusive Conditional |
---|
...
if (x > 1)
console.log("Goodbye");
else
console.log("Hello");
console.log(", my dear"); |
Pre Test |
---|
Option 1let i: number = 0;
while (i < 10) {
console.log(i);
i++;
} | Option 2for (let i: number = 0; i < 10; i++)
console.log(i); |
Post Test |
---|
let i: number = 0;
do {
console.log(i);
i++;
} while (i < 10); |
Complex Control |
---|
...
for (let i: number = b; i > 1; i/=2) {
if (i == 3)
continue;
if (i == a)
break;
console.log(i);
} |
All keys or indices |
---|
let o = {x:1, y:2};
for (let key in o) {
console.log(o[key]);
} |
All values |
---|
let o = {x:1, y:2};
for (let value of o) {
console.log(o);
} |
Subactivity simple |
---|
Functiongreet();
function greet(): void {
console.log("Hello");
} | MethodGreeter.greet();
class Greeter {
public static greet(): void {
console.log("Hello");
}
} |
Accept Event |
---|
someEventTarget.addEventListener("triggerGreet", greet); |
Send Signal |
---|
let event: Event = new Event("triggerGreet");
someEventTarget.dispatchEvent(event); |
Accept Time Event |
---|
window.setTimeout(greet, 2000); |
Output
starting to wait for something
doing something else
done waiting for something
The class diagram models complex structures of information used in the system designed. The focus is less on the activities within the system, but on the data those activities are applied to. Designing these structures well is crucial for the performance, stability, maintainability as well as the producibility of the system. Thus, in complex systems, the class diagram and the activity diagrams are created in parallel, each reflecting on the other.
Carefully consider the arrows, they symbolize different relationships. The right arrow symbolizes inheritance and points to the generalization. The left arrow is some kind of association, in this case objects of the class in the center know, use or contain objects of the associated class. This can also be bidirectional or, if the relationship is not yet to be further specified, just a line without a direction.
Symbol/Format | Meaning |
---|---|
+ | public |
# | protected |
- | private |
underlined | static |
italic | abstract |
<< interface >> | specifies interface |
<< enum >> | specifies enumeration |
The designer tries to identify classes of objects in the domain of the problem to be solved. During this process it is required to iterate over these five questions over and over again. Considering one object ...
Question | Infers |
---|---|
what does it have? | attributes, properties |
what can it do? | methods |
what does it know? | parameters to methods, entanglement |
what is it? | inheritance, polymorphism |
who cares? | owner, maintainer, creator |
When answering those questions, the designer must strive to make every part of the system...
- only as "smart" as necessary
- as "dumb" as possible
While doing so the designer makes use of the four principles of object-oriented modelling:
- Abstraction
- Encapsulization
- Inheritance
- Polymorphism
let courses: Course[] = [];
let course: Course = { name: "Physics", students: [] };
course.docent = new Docent("Einstein", 71);
course.docent.addSkill("Relativity");
let student: Student = new Student("Heisenberg", 49);
course.students.push(new Student("Hawking", 8), student);
courses.push(course);
courses.push({
name: "Art",
students: [student, new Student("Dali", 46)]
});
for (let course of courses) {
console.log("Course: " + course.name);
if (course.docent)
console.log("• Docent: " + course.docent.getInfo());
else
console.warn("• No docent assigned to this course");
for (let student of course.students)
console.log("• Student " + student.getInfo());
}
Output
Course: Physics
• Docent: Prof. Einstein, age: 71
• Student 1: Hawking
• Student 0: Heisenberg
Course: Art
• No docent assigned to this course
• Student 0: Heisenberg
• Student 2: Dali
In order to design interactive applications there is at least one more thing necessary, which is not part of the UML-specification. At least for applications with a visible user interface, this needs to be defined and is done so with UI-Scribbles.
UI-Scribbles depict the layouts of screens and point out the elements used, especially those for interaction. It's far less about colors and shapes than about defining the core structures the following design process builds on. That's why this step comes right after the Use-Case-Diagram.
For a user interface to be created with standard DOM-elements, annotations in the scribble hint to the types of elements to use and possibly additional information like identifiers, css-classes and other attributes. Most importantly, the annotations define which elements will receive signals about the user interaction. Thereby, the starting points of the algorithms processing the user interaction are also defined.
The developers code must be self-explanatory. This requires the strict use of naming conventions. Use names, that clearly explain the activity or information addressed and don't be greedy with letters. Short names are allowed only in very small scopes or when their meaning is clear by convention, such as y
for a vertical position.
The names of variables and functions must start lowercase and follow the camelCase notation, with uppercase letters indicating the start of a new part in a compound name such as animalLion
. The names of variables describe an information or an object. Names of functions and methods strictly describe activities e.g. calculateHorizontalPosition(...)
or questions e.g. isHit()
Name formal parameters in a functions signature like variables, but prefix them with an underscore like _event: Event
Names of classes, interfaces and namespaces or modules start with an uppercase Letter and then follow the camelCase notation (PascalCase). The name describes exactly one object of that type, not an activity. E.g. ObjectManager
.
The names of enumerations and their elements are written all uppercase, with underscores seperating parts of the name e.g. EVENT_TYPES.EXIT_FRAME
Bad example from the DOM-API: getElementById(...)
vs getElementsByTagName(...)
. Only the little s
in the middle indicates that one returns a collection, not a single element. Better: getElementCollectionByTagName(...)
. However, in getElements(...)
, the s
is clearly visible, since it's the last letter.
Some prefixes may be helpful for finding names for variables, use is encouraged
Prefix | Example | Meaning |
---|---|---|
n |
nObjects |
an amount |
i |
iObject |
an index |
x , y , z |
xPos |
a direction or dimension |
min , max |
maxHeight |
boundaries |
For example, state
may have different meaning depending on the context. Machine.state
indicates something different than Address.state
. However, it is redundant to write Machine.stateTheMachineOperatesIn
or Address.stateAsThePoliticalEntity
, since the context is provided already by the namespaces. Use this instead of implementing redundancies.
Use comments sparsely! If you feel that some code needs commenting rethink it and the naming of its components. Remember that you need to maintain comments just as you need to maintain code. Otherwise comments are not only useless but obstructive. Comments are allowed in the following cases
- when using a documentation generator such as TypeDoc
- to display an explanation in the development environment e.g. when hovering with the mouse
- to mark regions in the code (use "# region" and "# endregion")
A function should not consist of more than 20 lines of code. If possible, split it up into smaller functions each of which has an explanatory name. This way, the calling function consists of multiple calls that are easy to read and interpret, and the concerns are distributed to smaller functions with the same qualities. Also, watch out for the size of classes, beware of monsters! Keep the number of attributes low
One function/method should care only about one concern and do this well
A function should not indent more than two levels. Use return statements not only at the end, but so called "early outs" and throw exceptions to keep indentation level low.
Order functions and methods in such a way, that the call sits above the called function in code. Reading from top to bottom, the code displays the hierarchy of calls making it possible to understand the overall structure first before diving into the details.
Strictly use explicit typing wherever possible. The type any
is prohibited.
Always end statements with semicolon.
Literal strings should be enclosed in double quotiation marks e.g. "Hello World!"
Are simply disallowed. Never use a literal value in a function call when its meaning is not extremely obvious (e.g. Math.pow(x, 5)
to retrieve x to the power of 5). In all other cases, define a variable with a explanatory name and assign the literal value to it. This way, there is a value and a meaning to it, and its value can be changed in a single place.
Use one file per class. Interfaces may be compiled with classes using them and exceptions are allowed for small classes only used within the scope of the file. Use PascalCase for filenames, exactly the same name as the classes.
https://github.com/basarat/typescript-book/blob/master/docs/styleguide/styleguide.md
https://github.com/Platypi/style_typescript
https://github.com/excelmicro/typescript
Eine Liste der wichtigsten Operatoren in TypeScript / JavaScript
Op | Beispiel | Beschreibung |
= | x = 7; | Der Variablen auf der linken Seite wird der Wert auf der rechten Seite zugewiesen |
Op | Beispiel | Beschreibung |
+ | x + 5; | Addition: Liefert das Ergebnis der Addition des linken und rechten Wertes ohne Speicherung. |
- | 17 - x; | Subtraktion: Liefert das Ergebnis der Subtraktion des linken und rechten Wertes ohne Speicherung. |
* | 5 * x; | Multiplikation: Liefert das Ergebnis der Multiplikation des linken und rechten Wertes ohne Speicherung. |
/ | x / 2; | Division: Liefert das Ergebnis der Division des linken durch den rechten Wert ohne Speicherung. |
% | x % 10; | Modulo: Liefert den Rest der ganzzahligen Division des linken durch den rechten Wert ohne Speicherung. |
Op | Beispiel | Beschreibung |
+= | x += 19; | Additionszuweisung: Der Wert der Variablen wird um den rechtsstehenden Wert erhöht. Gleichbedeutend mit x = x + 19; |
-= | x -= 3; | Subtraktionszuweisung: Der Wert der Variablen wird um den rechtsstehenden Wert vermindert. Gleichbedeutend mit x = x - 3; |
*= | x *= 100; | Multiplikationszuweisung: Der Wert der Variablen wird um den rechtsstehenden Faktor erhöht. Gleichbedeutend mit x = x *100; |
/= | x /= 4; | Divisionszuweisung: Der Wert der Variablen wird um den rechtsstehenden Divisor vermindert. Gleichbedeutend mit x = x / 4; |
++ | x++; | Inkrement: Der Wert der Variablen wird um 1 erhöht. Gleichbedeutend mit x += 1; |
-- | x--; | Dekrement: Der Wert der Variablen wird um 1 vermindert. Gleichbedeutend mit x -= 1; |
Op | Beispiel | Beschreibung |
== | x == "AB" | Wertgleichheit: Liefert den Wert true, wenn die Werte auf der linken und rechten Seite gleich sind. Vorsicht bei floats!! |
=== | x === "42" | Wert- und Typgleichheit: Liefert den Wert true, wenn die Wertgleichheit zutrifft und beide Ausdrücke auch vom gleichen Typ sind. |
!= | x != "AB" | Ungleichheit: Liefert den Wert true, wenn die Werte auf der linken und rechten Seite unterschiedlich sind. |
> | x > 2.32 | Größer: Liefert den Wert true, wenn der linke Wert größer als der rechte ist. |
< | x < 2.32 | Kleiner: Liefert den Wert true, wenn der linke Wert kleiner als der rechte ist. |
>= | x >= 2.32 | Größergleich: Liefert den Wert true, wenn der linke Wert größer als der rechte oder genau gleich ist. |
<= | x <= 2.32 | Kleinergleich: Liefert den Wert true, wenn der linke Wert kleiner als der rechte oder genau gleich ist. |
Op | Beispiel | Beschreibung |
&& | x>2 && x<9 | Und: Liefert den Wert true, wenn der linke und der rechte Ausdruck beide den Wert true haben. Hier, wenn x zwischen 2 und 9 liegt. |
|| | x<2 || x>9 | Oder: Liefert true, wenn wenigstens einer der beiden Ausdrücke true ist. Hier, wenn x außerhalb des Bereichs 2 bis 9 liegt. |
! | !(x > 10) | Nicht: Negiert den Ausdruck, liefert also true, wenn der folgende Ausdruck false ist. Hier, wenn x <= 10. |
Damit werden direkt Bitmuster manipuliert. Siehe MDN
- Object Management Group. (2015, Mai). About the Unified Modeling Language Specification Version 2.5. Abgerufen 18. Januar 2020, von https://www.omg.org/spec/UML/2.5
- Kecher, C., Salvanos, A., & Hoffmann-Elbern, R. (2017). UML 2.5: Das umfassende Handbuch. Ausgabe 2018. Inkl. DIN A2-Poster mit allen Diagrammtypen. Bonn: Rheinwerk Verlag GmbH.
- Oestereich, B., & Scheithauer, A. (2013). Analyse Und Design Mit Der Uml 2.5: Objektorientierte Softwareentwicklung (German Edition) (11th 11., Umfassend Uberarbeitete Und Aktualisierte Auflage ed.). München: Walter de Gruyter.
- Microsoft. (2020). TypeScript - JavaScript that scales. Abgerufen 18. Januar 2020, von http://www.typescriptlang.org/