-
Notifications
You must be signed in to change notification settings - Fork 5
/
2017-1 Software Architecture And Engineering.fex
1275 lines (1149 loc) · 54.7 KB
/
2017-1 Software Architecture And Engineering.fex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Software Engineering
====================
A collection of techniques, methodologies, and tools that help with the production of
a high quality software system
with a given budget
before a given deadline
while change occurs
constraints of good software:
Scalability
Repairability
Portability
Reusability
Understandability
Maintainability
Security
Usability
Reliability
Robustness
Performance
Correctness
Interoperability
Evolvability
Verifiability
Backwards Compatability
Software Design:
informal Modeling: abstract models to simplify understanding (UML)
formal Modeling: formally write down the model; has tool support (alloy)
Design principles: how to fit reused class into class hiearchy?
Architectural & design patterns: general, reusable solutions to common design problems
Testing:
function testing: focuses on input / output behaviour (given functionality; how to structure input to find all variants?; needs only method signature)
structural testing: uses design knowlegde about algorythms to figure out corner cases
atomatic test case generation: generate test cases that execute a given path throught the program
dynamic program analysis: focuses on subset of program behaviours; and proves their correctness (under approximation)
static program analysis: capture all possible program behaviours in a mathematical model; and prove properties (over approximation)
Static Analysis:
Mathematical foundations
Abstract interpretations
Practical applications
Software development:
requirements elicitation:
what the customer really wants
requirements engeneering (find out & write down what the customer wants)
requirements validation (crossreading)
requirements elicitation (create scenarios, use cases and write formal specifications)
design:
how to display it
system design (use linked list or array list)
detailed design (choose behavour in corner cases, like if key not found in dictionary: exception or return null?)
implementation: implement it
validation: check if it fits the requirements
why projects fail:
lack of user input (13%)
incomplete requirements (12%)
Changing requirements (11%)
Unrealistic expectations (10%)
REQUIREMENTS ELICITATION
========================
requirements engeneering
describe user's view
identify what not how
part of requirements:
functionality
user interaction
error handling
environemental conditions
NOT part of:
system structure
implementation technology
system design
development methodology
functional requirements:
functionality:
what is the software supposed to do
relationship input to output
response to abnormal situations
exact sequence of operations
validity checks on the inputs
effect of parameters
external interfaces:
interaction with people, hardware, other software
detailed descriptions of all inputs & outputs (description of purpose, source of input / destination of output, valid range, accuracy, tolerance, units of measure, relationships to other inputs/outputs, screen & window formats, data & command formats)
non-functional requirements:
performance:
speed, availability, response time, recovery time
static nummerical requirements: number of installations, simultanious users, amount of information handled
dynamic numerical requirements: number of transactions processed in timeframe (ex: 95% in under 1s)
attributes (quality):
potability, correctness, maintainablity, security
design constraints:
operating environement
standart compliance: report format, audit tracking
implementation requirements: tools, programming languages, technology & methodology -> fight for it!
operations requirements: administration & management of the system
legal requirements: licensing, regulation, certification
quality criteria of requirements:
correctness: requirements represents the clients view
completeness: all possible scenarios are described, including exceptional behaviour
consistency: requirements do not contradict each other
clarity: reuqirements can only be interpreted in one way
realism: reuqirements can be implemented & delivered
verifiability: requirements can be verified (tests can be written to prove this)
traceablity: each feature can be traced to a set of functional requirements
general example:
use time units / specific units to prove your point (not "fast", "in 2 seconds")
requirements validation:
the sooner an error is found, the cheaper
occurres after requirements engeneering
reviews: by developers & clients
prototyping: throw-away prototypes (user interface or fully functional) to show functionality, study feasability, give clients an impression
requirements elicitation
indentify the following & write formal specification
is understood by customers & users
information sources: enduser, client, documentation, observation of tasks
actors:
represent roles (kind of user, external system, physical environement)
to ask: who supported, who executes what, which environement
scenarios:
document behaviour from the user's view
describes common case
focus on understandability
instance of an use case
how to indentify: what are the tasks needed, what information is accessed by the user, what input needs the system, which events needs to be reported
use cases:
describes all edge cases
focus on completeness
list of steps describing interaction between actor & system to archieve a goal
use case contains:
unique name (edit entry)
initiating & participating actors (admin)
flow of events (steps)
entry conditions (at least one entry must exists)
exit conditions (the entry has been updated)
exceptions (system faillure)
special requirements (admin needs keyboard)
nonfunctional requirements:
definied together with functional because they have dependencies (help function for better usability)
typically contains conflicts (speed -> C, maintainability -> C#)
DESIGN
======
mastering complexity:
decomposition system
partition overall development effort
support independet testing & analysis
decouple parts of a system so changes to one part do not affect other parts
permit system to be understood as a composition of mind-sized chunks to be understood one at a time
enable reuse of components
System design:
determine software architecture as composition of subsystems
components: computation units with specified interface (databases, layers)
connectors: ineractions between components (method calls, events, pipes)
Detailed design:
choose among different options: data structures, algorythms, subclass hierarchies
things to choose: NULL permitted as value? thread safety? available methods?
concepts: copy-on-write, destructive updates, reference counting, lazy initialization, valid entries, optimizations change behaviours?, shared elements
MODELING
========
Design documentation:
document the design decisions made (with NULL values, lazy-initialization, etc)
design decisions:
determine how code should be written
made in initial development, inheritance, writing client code, during maintenance
must be communicated to different developers
source code not suffient, as it only contains the obvious information -> developers require difficult infos to extract from code to be documented
document:
result values of method (and when they occurr)
side effects of methods
consistency conditions for data strucutures (null values etc)
how data structures evolve over time (arraylist -> when is array resized?)
whether objects are shared
which details are essential, which are incidential (functionality vs performance optimization)
document for clients:
how to use the code
document the interface
how to call correctly constructors & methods correctly:
any precondition to the state of the object?
allowed values?
what is returned by methods:
how are failures dealt with
what are valid responses
how method calls affect state:
heap effects (effects on general state, state of a passed objects)
other effect as thrown exceptions
runtime of method (linear or quadratic)
also document: public fields, supertypes
interface documentation:
global properties which are preserved
global requirements by all methods
consistency: client visible invariants (list item order)
evolution: property of sequences of states (immutable structure has always same content)
abbreviations: requirements & guaranteed for all methods (thread safety)
document for implementors:
how does the code work
document the implementation
focus on WHAT properties are not HOW they are archieved
similar to interface: more details (include effects on fields), includes hidden methods
data structure more prominent: properties of fields; internal sharing (if $shared is true then it is shared); invariants (list is not changed)
documentation of algorythms: justification of assumption ($var not null)
documentation key properties:
methods & constructors: arguments & input state, results & output state, effects
data structures: value & structural invariants, one-state & temporal invariants
algorythms: behaviour of snipptes, explanation of control flow, justification of assumptions
how to document:
comments: simply write text; has limited tool support
types & modifiers: final, private etc; tool support: has static, runtime checking & auto-completion
effect systems: produces overhead; read-write effects, allocation/de-allocation, locking, exceptions (try, catch or "throws IOException")
metadata: annotations / attributes for syntactic & semantic information, tool support: typechecking, static, dynamic processing
assertions: specify semantic properties in code; tool support: runtime, static checking, testcase generation
contracts: assertions for interfaces & implementations, method pre- & postconditions, invariants; tool support: runtime, static cehcking, testcase generation
techniques:
tradeoff between overhead, expressiveness, benefit, precision
more formal -> more tool support
mix different techniques
informal modeling
design iteratively; underspecification and then add details, and design decisions (algorythms, data structures, control flow)
specific different views on design: architecture (crash possible?), test generation (all states reached?), security review (authorization valid)
design specification: source code must decide, but design descisions difficult to extract
with UML
strengths: describe particular views, omit information or specify it informally, graphical notation makes communication easier
weaknesses: precise meansing unclear, incomplete / informal model lack tools support, many details are hard to despict
modeling:
abstraction from reality: objects & relations
simplifications: ignore details depending on the purpose of the model
draw conclusions for difficult szenarios by using the simple steps of the model
dealing with complexity
static modeling: describe structure
dynamic modeling:
describe behaviour
sequence diagrams: describe collaboration
state diagrams: discribe lifetime of single object
UML
===
Unified Modeling Language:
text, graphical notation
for: documentation, analysis, design, implementation
OMG (object management group) standard recommended
notations:
case diagrams: requirements
class diagrams: structure
interaction diagrams: message passing
state & activity diagrams: actions
implementation diagrams: component model (depdencencies) and deployment model (structure of runtime system)
OCL (object constraint language)
classes:
name (required)
attributes with Type (name : String)
methods with Signature & Type (getName(force: Boolean) : String)
instances:
name:type (underlines, name is not required)
attributes represented with their values
associations:
can be: sends a message, creating, attribute of value, receives a message
line with optional roles (employer, employee) and optional label (works for)
can contain multiplicity (city 1 --is capital of-- 0..1 country) (or 3..* for many)
can be directed (person ---> company); one or unidirectional
aggregation:
arrow (with scewed rectagle as arrow head, not filled out)
example: Professor -----<WHITE> Group
"belongs to"
no sharing
composition:
arrow (with scewed rectagle as arrow head, filled out)
example: Room -----<BLACK> Building
"is part of"
no sharing
generalization:
arrows, with traingle as head
example: Professor ----|> Person
"is a"
inherits attributes & methods
dynamic modelling:
make only for classes with significant dynamic behaviour
use only relevant attributes
sequence diagrams:
instances of actors & objects as columns
rows as time units
method calls as arrows which connect the different columns
grauer balken in einer column zeigt wie lange die aktion geht, startet bei method call (arrow to the column), stopt bei return (arrow from column away)
creation / descruction: arrow to object (so column starts more to the bottom), cross means deconstruction (by garbage collector)
views: can draw rectangle (with left top corner has description)
write "par" to make method calls parallel
write "alt" for if/else branches, dividing alternative action with a dashed line, writing the condition or [else] at the left
state diagramms:
black point as start, arrow to states (rounded rectagle), allow to ned states
arrows: contains event [condition] (not required) and specified action. example descriptions: "open()", "[low memory]", "after 10s"
endmarker: back point with cycle around
state: contains do activity, entry, exit action (activity which get executes in state (do), action on reaching the state (entry), action on leaving (exit))
event: something that happens at specific point in time (time event, message receive)
action: operation in response to event (computation)
activity: operation performed as long as object in specific state (continuous computation)
contracts:
OCL (object constraints language)
used to specifiy: invariants of objects, pre/post conditions of operations, conditions
special support for: navigation thorugh UML, assiciations
can use: self (as own context), attributes, role names, side-effect free methods, logical connectives, operations in integers / sequences
example:
context Person inv:
self.age >= 0
context Person inv:
self.Dog.age >= 0
context Person::Work(time:int)
pre: time >= 0
post: HasWorked() = HasWorked@pre() - time
mapping models to code:
MDD: model driven development
generate code from models
advantages: support many platforms, avoid boilerplate code, leads to uniform code, enforce coding conventions, models are not mere documentation
problem:
abstraction mismatch (not always possible to map to code, modle should not depend on specific language)
specification incomplete ("open()") / informal ("all conditions met")
switching between model & code: modifications of code (due to the the stuff mentioned above) has to be synced with models
reality:
works in specific domains (business process modelling)
works for basic properties
formal models:
notation & tools are based on mathematics (and therefore precise)
describe some aspects of a system
enable automatic analysis (find ill-formed example, proving properties)
ALLOY
=====
what
formal modelling langauge based on set theory
specify collection of constraints
alloy analyzer generates example based on constraints
signatures:
like a class
set of atoms (instances)
different sig -> different sets
example:
sig Person {}
sig Professor extends Person {} //prof is in the set of person
abstract sig Human {}
lone sig God {} //one or none
one sig Truth {} //exactly one
some sig Person {} //some is default, 1 or many
fields:
fields declares relation of atoms
example:
sig Person {
leader : one God //leader or type God, exactly one, is the default
hero: lone Person //may has a hero, may has not
children: set Person // 0 or many children
parents: some Person //1 or many parents
}
set operators:
union: +
intersection: &
difference: -
subset: in
equality: =
cadinality: #
empty set: none
universal set: univ
relation operations:
cross product:
->
creates a tuple
sig State {
aircraftLocation: Aircraft -> AircraftLocation
} {
all a: Aircraft | some ap : Airport | (a -> ap) in s.aircraftLocation
}
relational join:
.
connects properties
sig Person {
friend : Person
} {
all f : friend.friend | all
}
transposition:
~
reverses relation
sig Person {
friends: set Person
} {
friends = ~friends
}
transitive (reflexive) closure:
^, *
FSObject in Root.*contents
(File+Dir-Root) in Root.^contents
constraints:
negation: ! or not
conjunction: && or and
dijunction: || or or
implication: => or implies
alternative: else
equivalence: <=> or iff
quantifications: no, some, lone, one, all
some rules:
#{ f: FSObject | f inFile + Dir } > 0
#(File + Dir) > 0
all p : Person | p.hasFriend
all p1, p2: Person | p1 in p2.*friend
all p1 : Person, p2 : Professor | some p3 : Person | p1 = p2 or p3 = p2
all disj p1, p2 : Person | p1 != p2
all b : bookings | this in b.consistsOf
all disj s, t: Student | s.id != t.id
all i: ID | one s: Student | s.id = i
all s: Student | (s.university != none) <=> (s.isLegal = True)
predicates & functions:
predicates are named formulas:
pred isLonely[p : Person] { all p2 : Person | no p in p2.friend }
functions are named expressions:
fun loneyFriends[p: Person] : set Person { all p2 : Person | p2 in p.friend | isLonely[p2] }
can run predicate or function to find examples
run loneyFriends
run loneyFriends for 5
run loneyFriends for 5 Friends, 6 Professor
run loneyFriends for exactly 5 Friends
run loneyFriends for 5 but (exactly) 3 Friends
facts:
add constraints that always hold
fact { all p : Person | #(lonelyFriends[p]) = 0 }
assertions:
assert my_assert { all p : Person | #(lonelyFriends[p]) = 0 }
check my_assert for 5
under/overconstrain:
underconstraining: permit impossible structures
overcontraining: disallows valid structures
inconsistencies: if fact (1 != 0) -> all will pass!
avoid overconstraining: just use assertions wherever possible
alloy for dynamic:
pred init[u: User]{
#u.forSale = 0
}
pred offer[u, u': User, i: Item] {
(#u.forSale < 3 or u in PremiumUser) =>
(u'.forSale = u.forSale + i)
else
(u'.forSale = u.forSale)
}
pred inv[u: User] {
#u.forSale > 3 implies u in PremiumUser
}
assert invHolds {
all u: User | init[u] => inv[u]
all disj u_before, u_after: User |
all i: Item | (inv[u_before] && offer[u_before, u_after, i] => inv[u_after]
}
alloy for dynamic with states:
pred update[a, a':Person] {
}
pred removeAll[a, a':Person {
a'.friends = none
}
pre inv[] {
}
assert initEstablishes { all s’: State, … | init[ s’, … ] => inv[ s’ ] }
check initEstablishes
assert opiPreserves { all s, s’: State, … | inv[ s ] && opi[ s, s’, … ] => inv[ s’ ] }
check opiPreserves
open util/ordering[ State ]
fact traces {
init[ first ] &&
all s: State - last |
(some… | op1[ s, s.next, … ]) or … (some… | opn[ s, s.next, … ])
alloy simple automata example:
sig Counter { n: Int }
pred inc[c, c': Counter] { c'.n = c.n.add[Int[1]] }
pred init[c: Counter] { c.n = Int[0] }
fact traces { init[first] && all c: Counter - last | inc[c,c.next] }
analyzing models:
consistency: F is consistent if it can be fullfilled there_is s * C(s) ^ F(s)
validity: if it evaluates to true always when all constraints are satisfies for_all s * C(s) => F(s)
check for valid:
sig Node { next : Node}
check for 3
-> generate (1,1), (1,2), (1,3), (2,1), ...
-> generate constrains from formulas
-> filter out generated model which do not fullfil constraints
consistency checking (done with RUN command):
so alloy translates constrains & formula and tries to find assignement -> if yes, display model
validity checking (done with CHECK command):
alloy checks for invalids because its faster (inverse validity definition)
so alloy translates constrains & negated formula and tries to find assignement -> if no, all valid
COUPLING
========
Representation exposure
if modules expose internal data to clients they get tighly coupled:
data representation is difficult to change
modules cannot maintain invariants
concurrency very complex
unexpected side effects if exposing sub-objects / structures
shared data structures: modules get coupled, problems with changing, concurrency, side effects
approach 1 (restricting access to data):
can only access to simple restrictive interface
information hiding
non-leaking: do not return references to internal objects (clone if necessary)
non-capturing: do not store arguments
facade pattern: single, simplified interface without hiding the details completely
approach 2 (making shared data immutable):
copies (to change data eventually) remain run-time performance problem
flyweight pattern: pool of Flyweight; client requests one with a key; if not found it is created and added to a pool, then return to client
approach 3 (avoid shared data):
just copy changed data
pipe & filter:
data flow for communication; no common state
filter: read data from input; compute; write data to output
pipe: streams; join / split connectors (the lines between the filters)
properties: data is processes incrementally, filter independent, output beginns before input finished, filters dont know the others
filters:
input/output stream; may lookahead, may have local state, repeat till no more input
example: split duplicate, split RR
fusion: combine filter; reduce communication cost, less paralellization
fission: split filters; introduce parallelism, more communication needed
strenghts: reuse (if filters have same format), ease of maintenance (single filters can be replaced easely), parallelism
weakness: sharing global data is expensive, difficult to design, not interactive, error handling very difficult, no complex data can be passed (ASCII on linux)
procedural coupling:
problems: reuse (multiple objects coupled, no seperation of concerns), adaptation (changes in callee may needs change in caller)
approach 1 (move code):
move code to seperate concerns;
common to duplicate code to not be dependent on other companies
approach 2 (event based style):
components generate events, and register for events
generators do not know subscribers
observer pattern: subject with Attach() Detach() Notify() { call Update() for all attached subjects }
model-view-controller:
controller: handle input
model: contains core functionality
view: displays info
implications: user-interface & models must stay consistent
aufbau: view ---sends events--> controller <--receives update notifications-- model
strenghts: stong support for reuse, adaptation
weakness: loss of control, ensuring correctness difficult
approach 3 (restricting access):
enfore policy what can be called by what module -> "layering"
example: presentation, logic, data
strengths: patition complex problems, maintenance easy, reuse (can exchange layers)
weakness: performance
class coupling:
inheritance couples sub to superclass: changes in super may break subclass, limited options for other inheritance
approach 1 (replace inheritance with aggregation):
replace with aggregation, subtyping, delegation
aggregation:
take methods needed from another class and present it as own
example: have object of class cat inside dog; and expose properties needed for dog; but let cat execute it (dog.walk = cat.walk)
approach 2 (use interface):
replace occurrence of class name with supertypes: use the most general type needed (or interfaces)
let clients construct the superclass (__construct(IInterface $implementation));
but difficult to test
approach 3 (delegating allocation):
dependency injection: allocations are defined in config file, framework does the initialization
factories:
delegate allocation to special class (abstract factory) which does this
concret factory (which implementes the abstract factory) is chosen by the client
low coupling is design goal
trade offs: perfornace & convenience, adaptability, code cuplication
coupling to stable (framework) classes less critical
ADAPATION
=========
changes:
software changes frequently
new features, interfaces, performance tuning
parameterization:
prepare modules for change
parametric in:
values they manipulate: not two explicit; use list
data structures they use: interfaces & factories
types they use: use generic types / base types
algorythms they apply: use delegates
strategy pattern:
interface Selector<D> ("Strategy")
class MySelector<D> implements Selector<D> ("Strategy1")
client deals with Strategy<D>
strategy is selected /passed by method call
encapsulate different algorythms from client
specialization:
dynamic method binding: methods can be specialized by overriding & dynamic method binding (inheritance)
can be understood as a case distinction
drawbacks dynamic method binding:
reasoning: invariants maintaining?
testing: more potential behaviours
versioning: harder to evolve without breaking subclasses
performance: overhead of method lookup at runtime
-> choose binding of method carefull; apply final or virtual keywords
state pattern:
Context -> state (which is implemented by ConcreteState1, ...)
context has state as variable; and can choose at runtime which state to apply (can change in between executions)
state changes behaviour
visitor pattern:
traverse structure of objects
IVisitor which contains a method overload for each element needed to be traversed
IElement contains Accept(IVisitor v) {v.Visit(this);} method
-> visitor is now central point to print / save all elements
summary:
parameterization: supply different arguments to modify behaviour
specialization: adding subclasses / override methods to modify behaviour
TESTING
=======
why bugs:
predicting the behaviour of source code is difficult
mistakes: unclear requirements, wrong assumptions, design & coding errors
increase reliability:
fault avoidance: detect faults statically, development methododologies, review, program verification
fault detection: detect faults while executing the program; testing
fault tolerance: recover from faults at runtime, adding redundancy (n-version programming)
testing general:
successful test find error
error is deviation from desired outcome (by function, non-function requirements)
execute program to find error
impossible to test fully:
theoretical: termination
pratical: pohibitive in time & cost
stages:
requirements elicitation: system tests
system design: integration tests
detailed design: unit test
test harness:
test framework
testdriver: applies testcases to Unit Under Testing (UUT)
UUT uses Test Stub's, implementations of components used by UUT (provides fake data, simulates environement)
Unit Testing:
testing individual subsystems;
confirm each subsystem works correctly
need unit test for each input values -> to get reasonable coverage need to test multiple
parameterized unit tests: unit tests with arguments which can be set by the test framework, avoid boilerplate, allows generation of test data
test execution:
execute test cases, re-execute after every iteration
regression testing: ensuring everything still works after applying changes
rules:
fully automatic: test must be excutes fully automatic and check their own results
test suite: reduce time needed
run frequently: at least once a day
unit test to expose bug: if a bug report received; write unit test that exposes it
incomplete testing > no testing
boundary conditions: concentrate on these cases "edge cases"
exception testing: test exceptions when things go wrong
write tests that catch most bugs, instead of writing none
integration testing:
testing groups of subsystems; and eventually the whole system
confirm interfaces between subsystems
bottom-up (top not implemeneted yet), top-down (bottom submodules not implemenetd yet), big-bang approach (test all in once)
system testing:
test entire system
determine if system fulfills functional & non-functional requirements
strategies:
functional requirements:
functional tests
goal: test functionality
test system as black box
testcases based on use cases
desribe: input data, flow of events, resuts (which are checked)
non-functional requirements:
performance tests
goal: test performance
clients understanding of requiements:
acceptance test
goal: demonstrate that the system meets the requirements
performed by the client!
alpha test: customer @ developer; which is ready to fix bugs
beta test: @ clients site, developer not present, realistic workout in target environement
user environement:
installation tests
independent testing:
programmers test happy paths because they have vested interest not to find mistakes
testers must seek to break the software -> should be independent
all but unit tests should be performed by testers
facts:
the developer should test himself
testers are involved from the start
testers work together with developers at test suite
testers are not solely responsive for quality of software
testing steps:
select what will be tested
select test strategy
define test cases
create test oracle (expected results)
testing strategies:
exhaustive testing: check UUT for all possible inputs
random testing:
select data uniformly
goal: cover corner cases
advantage: avoids designer bias, tests roboustness (reation to invalid input)
disadvantage: treats all inputs the same
for all test stages
functional testing:
requirements knowledge determines test cases
goal: cover all requierements
find incorrect functions, interfaces errors, performance leaks
limitations: does not detect design / coding errors, does not reveal errors in specification
for all test stages
structural testing:
design knowledge determines test cases
goal: cover all code
limitations: focus on code and not requierements, requires design logic (only programmers know), highly-redundant tests
for unit testing
FUNCTIONAL TESTING
==================
partition testing:
divide input into equivalence classes
choose test cases for each equivalence class
selecting representative values:
after partitioning; select concrete values from each of the partitions to test
large number of errors occurr at boundary of the input domain -> so select elements of edge of equivalence class & some from the middle
cominatorial testing:
combine boundary testing & equvalence classes: too much example to test if combined
select specific combinations: semantic constraints, cominatorial selection, random
do not select unnecessary combinations (which have no influence to each other)
semantic constaints: at least one test case for each constraint
pairwise combinatorial testing:
two or three values interactions reveal most errors
focus on all possible inputs for each pair of inputs
reduces the number of inputs drastically
important if a lot of system configuration needs to be tested
combine with other approaches
STRUCTURAL TESTING
==================
why
detailed design & coding introduces behaviours which are not specified
White-box test a unit to cover a large portion of its code
control flow testing:
basic block: block of code with one input & one output point; upon entering the rest of the code is executes once, in order
intraprocedural control flow graph (CFG):
top to bottom
entry block
arrows to each basic block
label @arrows have condition written on it (example b2 = (i < a), produces two arrows b2, -b2)
point to exit block when finished
coverages:
statement coverage:
how many portions of the CFG are executed (nodes & edges)
#excuted / #total
-> but still possible to miss bug
branch coverage:
test all possible branches in control flow (edges)
complete branch coverage implies complete statement coverage
-> still possible to miss bugs
path coverage:
test all possible paths (sequence of branches)
complete path coverage implies complete branch coverage
-> not feasible with loops (arbitrary # of paths)
loop coverage:
for each loop, test 0, 1, and 1+ iterations
coverage = #loops with 0,1,1+ iterations / #loops * 3s
data flow coverage:
evaluated with DU pairs
coverage = #DU-pairs / used DU-pairs
method calls:
CFG treat method calls as simple statments; but they may invoke different code depending on state
testing dynamically bound by viewing it as a case distinction for all possible implementations -> then do branch testing
but this leads to combinatorial explosion -> use semantic constraints & pairwisecombinations testing
exceptions:
documented exception (checked) (as CollectionEmptyException): can be treated like branches
undocumented (unchecked) exception (MemoryOverflowException): impractical to represent all in CFG
checked exception:
invalid conditions outside the immediate control of the program (invalid user input, network outage)
are declared in method signatures in java
test like normal control flow
unchecked exception:
defects in the program or execution environement (illegal arguments, division by null)
ignore exceptions thrown by other methods, but consider throw staments in own code
never use unchecked for control flow! (like NullPointerException)
data flow coverage:
Test those paths where a computation in one part of the path affects the computation of another
variable definition: basic block that assigs a variable to v
variable use: basic block that uses the assigned variable
definition clear path: n1, ..., nk where n1 defines the variable, and nk uses it -> do not necessarily go from entry to exit
DU-pairs:
defintion-use pair (DU pair): defintion clear path in the CFG
DU-pair coverage: test all paths that provide a value for variable use
(1,3): 1 is LineNr where the variable was defined, 3 is LineNr where the variable is used
determining DU pairs:
Reach(n): contains all the defintions made from before (UNION from all paths)
ReachOut(n): contains all definitions which survive this line (most of the time Reach(n) == ReachOut(n))
evaluate Reach(n) & ReachOut(n):
1. make a table with columns lineNr(n), Reach(n), ReachOut(n)
2. start from top to bottom, with Reach(1) is empty (leere Menge)
3. for each line, if variable is assigned put variable_name_line_number into ReachOut(n), else put Reach(n)
4. join in loops and gotos
evaluate DU pairs:
1. build tbale as described above
2. get all reading locations of variable in question
3. for each reading location (say line 6), look at Reach for the corresponding line (say var_1, var_3) and build any possible combination ((1,6),(3,6))
complete DU coverage needs more than one loop iteration
choose testing that maximizes branch & DU-paris coverage
measure DU-pair coverage with maps
not all DU-pairs are feasible (has to over-approximate)
DU-pair anomalies may detect errors: double-definition, use of unassigned, no usage
interpreting coverage:
high coverage does not mean code is well tested, but contrary applies
coverage tools help to find parts of software which are not well tested
test suite grows exponential with coverage
criterias lead to better testing than random testing
more demanding coverage cirteria leads to bigger test suites but not to detecting more bugs
cost-efficieny of all test aproaches about the same
experimental evaluation: seed defects in code; test with test suite and check if it is catched
SOFTWARE ENGENEERING
===================
pure methods:
fullfill both properties:
i) does not modify any objects which existed before calling (but may modify objects it has created)
ii) will return the same result if the state is same (same object, same arguments)
example for pure methods: hash()
example for non-pure: returnRandomValue()
c# contracts:
public class MyCheckedClass {
private int[] elems = new int[10];
[ContractInvariantMethod]
private void ObjectInvariant() {
Contract.Invariant(elems != null);
}
private void Set(int[] myElements) {
Contract.Requires(myElements != null);
Contract.Ensures(Contract.OldValue(elems) != elems || Contract.OldValue(elems) == myElements);
Contract.ForAll(0, myElements.Count() - 1, i => elems[i] == myElements[i])
}
}
patterns:
creational pattern:
create objects in a manner suitable for the situation
abstract factory:
creation method which returns the Factory itself, which in turn then creates new objects
parses xml configuration files to look for the implemenetations to use; then returns a factory with that injected information
-> newInstance() method
builder:
creation method which returns reference to itself
php property setter pattern, allows to set multiple props on same line
-> string.append
static factory:
creational method which returns an implementation of an abstract type / interface
used with compile-time / configuration data so factory knows what implementations to use
-> NumberFormat.getInstance()
prototype:
creation method return different instance of itself
create() method in entities
-> Object.clone()
singleton:
creation method returning same instance everytime
static class in hiding
-> getInstance() method, Dektop.getDesktop()
structural pattern:
relationships between objects
adapter:
taking an instance of a different abstract type & return a new instance which decorates/overrides the given instance
takes an instance and returns another instance which overrides the given instance
-> java.io.InputStreamReader(InputStream)
bridge:
taking an instance of a different abstract type & return a new instance which delegates/uses the given instance
takes an instance and returns another instance which is uses the given instance
-> java.NewSetFromMap(map)
composite:
behavioral methods taking an instance of same abstract/interface type into a tree structure
in tree, like AddNode()
-> java.awt.Container#add(Component)
decorator:
creational methods taking an instance of same abstract/interface type which adds additional behaviour
a IReader takes an IReader as constructor argument; internally may calls the passed IReader
-> InputStream has constructur taking instance of same type
facade:
behavioral methods which internally uses instances of different independent abstract/interface types
similar SyncApiService, redirect calls to the correct types
-> java.ExternalContext which uses HttpServletResponse, HttpServletRequest etc internally
flyweight:
creational methods returning cached instance
pool of availble objects; flyweigth is asked to return specific one; takes it from the pool or creates new one
-> Integer#valueOf(int), can be made with Boolean, strings, etc
proxy:
creational methods which returns an implementation of given abstract/interface type which in turn delegates/uses a different implementation of given abstract/interface type
ProxyUserService which uses the GeneralUserService. GetUserService() would the method be named
-> the services of a DAL in java
behavioural:
communication patterns between objects
chain of responsibility:
methods which (indirectly) invokes the same method in another implementation of same abstract/interface type in a queue
passing on certain input arguments based on the value of those, logger (by LOG_LEVEL) or middleware in slimPHP
-> java.util.logging.Logger#log()
command:
methods in an abstract/interface type which invokes a method in an implementation of a different abstract/interface type which has been encapsulated by the command implementation during its creation
RelayCommand()
-> javax.swing.Action
interpreter:
behavioral methods returning a structurally different instance/type of the given instance/type
parsing/formatting is not part of the pattern, determining the pattern and how to apply it is
-> java.text.Normalizer
iterator:
methods sequentially returning instances of a different type from a queue
IEnumerate etc
-> java.util.Enumeration
mediator:
behavioral methods taking an instance of different abstract/interface type (usually using the command pattern) which delegates/uses the given instance
Timer.schedule(TimeSpan span, Action action) -> the timer executes the action after the given time
-> java.util.Timer
memento:
behavioral methods which internally changes the state of the whole instance
Date->setDate("20.08.1995")
-> java.util.Date
observer:
methods which invokes a method on an instance of another abstract/interface type, depending on own state
register for events at observer, when event happen the observer will call you
-> javax.faces.event.PhaseListener
state:
behavioral methods which changes its behaviour depending on the instance's state which can be controlled externally
scheduler.ExecuteTask() -> waits longer or less long depending on CPU
-> javax.faces.lifecycle.LifeCycle#execute()
strategy:
methods in an abstract/interface type which invokes a method in an implementation of a different abstract/interface type which has been passed-in as method argument into the strategy implementation
list.sort() uses a comparator.compare() method to sort the elements