Skip to content

Instantly share code, notes, and snippets.

@cardasac
Last active May 13, 2019 01:37
Show Gist options
  • Save cardasac/b8b6e9d51cf09a10e996aadb1d557998 to your computer and use it in GitHub Desktop.
Save cardasac/b8b6e9d51cf09a10e996aadb1d557998 to your computer and use it in GitHub Desktop.
SE-2018

1.

a)

Definition

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle:

  • first the developer writes an (initially failing) automated test case that defines a desired improvement or new function
  • then produces the minimum amount of code to pass that test
  • and finally refactors the new code to acceptable standards.

Kent Beck, who is credited with having developed or 'rediscovered' the technique, stated in 2003 that TDD encourages simple designs and inspires confidence.

Test-driven development is related to the test-first programming concepts of extreme programming, begun in 1999.

Cycle

  1. Add a test
  2. Run all tests to see if new one fails
  3. Write some code
  4. Run test
  5. Refactor code
  6. Repeat

Advantages

A 2005 study found that using TDD meant writing more tests and, in turn, programmers who wrote more tests tended to be more productive. Hypotheses relating to code quality and a more direct correlation between TDD and productivity were inconclusive.

Programmers using pure TDD on new projects reported they rarely felt the need to invoke a debugger. Used in conjunction with a version control system, when tests fail unexpectedly, reverting the code to the last version that passed all tests may often be more productive than debugging.

Test-driven development offers more than just simple validation of correctness, but can also drive the design of a program. By focusing on the test cases first, one must imagine how the functionality is used by clients. So, the programmer is concerned with the interface before the implementation. This benefit is complementary to Design by Contract as it approaches code through test cases.

Test-driven development offers the ability to take small steps when required. It allows a programmer to focus on the task at hand as the first goal is to make the test pass. Exceptional cases and error handling are not considered initially, and tests to create these extraneous circumstances are implemented separately. Test-driven development ensures in this way that all written code is covered by at least one test. This gives the programming team, and subsequent users, a greater level of confidence in the code.

While it is true that more code is required with TDD than without TDD because of the unit test code, the total code implementation time could be shorter. Large numbers of tests help to limit the number of defects in the code. The early and frequent nature of the testing helps to catch defects early in the development cycle, preventing them from becoming endemic and expensive problems. Eliminating defects early in the process usually avoids lengthy and tedious debugging later in the project.

TDD can lead to more modular, flexible, and extensible code. This effect often comes about because the methodology requires that the developers think of the software in terms of small units that can be written and tested independently and integrated together later. This leads to smaller, more cohesive classes, looser coupling, and cleaner interfaces.

Because no more code is written than necessary to pass a failing test case, automated tests tend to cover every code path. For example, for a TDD developer to add an else branch to an existing if statement, the developer would first have to write a failing test case that motivates the branch. As a result, the automated tests resulting from TDD tend to be very thorough: they detect any unexpected changes in the code's behavior. This detects problems that can arise where a change later in the development cycle unexpectedly alters other functionality.

b)

Definition

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

It is mainly used to implement distributed event handling systems, in "event driven" software. Most modern languages such as C# have built-in "event" constructs which implement the observer pattern components.

The observer pattern is also a key part in the familiar model–view–controller (MVC) architectural pattern. The observer pattern is implemented in numerous programming libraries and systems, including almost all GUI toolkit.

What problems it solves

The Observer pattern addresses the following problems:

  • A one-to-many dependency between objects should be defined without making the objects tightly coupled.
  • It should be ensured that when one object changes state an open-ended number of dependent objects are updated automatically.
  • It should be possible that one object can notify an open-ended number of other objects.

Defining a one-to-many dependency between objects by defining one object (subject) that updates the state of dependent objects directly is inflexible because it couples the subject to particular dependent objects. Tightly coupled objects are hard to implement, change, test, and reuse because they refer to and know about (how to update) many different objects with different interfaces.

The solution it offers

  • Define Subject and Observer objects.
  • so that when a subject changes state, all registered observers are notified and updated automatically.

The sole responsibility of a subject is to maintain a list of observers and to notify them of state changes by calling their update() operation.
The responsibility of observers is to register (and unregister) themselves on a subject (to get notified of state changes) and to update their state (synchronize their state with subject's state) when they are notified.
This makes subject and observers loosely coupled. Subject and observers have no explicit knowledge of each other. Observers can be added and removed independently at run-time.
This notification-registration interaction is also known as publish-subscribe.

See also the UML class and sequence diagram below.

Class Diagram and Sequence Diagram

Link to Diagram

In the above UML class diagram, the Subject class doesn't update the state of dependent objects directly. Instead, Subject refers to the Observer interface (update()) for updating state, which makes the Subject independent of how the state of dependent objects is updated. The Observer1 and Observer2 classes implement the Observer interface by synchronizing their state with subject's state.

The UML sequence diagram shows the run-time interactions: The Observer1 and Observer2 objects call attach(this) on Subject1 to register themselves. Assuming that the state of Subject1 changes, Subject1 calls notify() on itself.
notify() calls update() on the registered Observer1 and Observer2 objects, which request the changed data (getState()) from Subject1 to update (synchronize) their state.

2.

a)

Display Booking

USE case realisation In the application domain, the bookings made can be viewed as an attribute of the restaurant itself. One possible strategy, therefore, would be to introduce a new class to represent the restaurant and give this class the responsibility of maintaining links to all the bookings made, and of locating and returning particular bookings when requested. With this assumption, the use case realization for displaying bookings can be refined to the next level.

This diagram shows what the system does in response to the message from the staff member. The resulting messages are shown originating from the activation on the booking system’s lifeline that corresponds to the system message, thus showing the nested flow of control that is characteristic of procedural message calls. The return message is explicitly annotated to show what data is returned.

Following this, a message is sent to update the current display. In terms of the system architecture, this message should be sent from the booking system to some class in the presentation layer, requesting that the display be updated. The ‘updateDisplay’ message in Figure above corresponds to a method that communicates this to the presentation layer; a mechanism by which this can be accomplished is described in the next chapter. It is likely that this method will be called on many different occasions, whenever the information to be displayed has changed.

Cancel booking

The use case for cancelling a booking has a more complicated structure than the ones realized so far, as the user’s participation in the use case consists of more than one interaction with the system. As described in Chapter 4, to cancel a booking the user must first select the booking to be cancelled, then cancel it and finally confirm the cancellation when prompted by the system to do so. A sequence diagram showing a possible realization of this course of events is shown in Figure 5.9.

This use case breaks down into two separate parts. First, the required booking is selected. There are many ways in which this could be done: perhaps the user clicks on the booking rectangle on the screen or enters some data which identifies the booking. This is realized in Figure 5.9 by a message ‘selectBooking’ from the user. At the analysis level, the exact nature of the information supplied by the user to identify the booking is left unspecified and the diagram simply indicates informally that sufficient is supplied to identify the required booking.

Cancel Booking In order to locate the required booking object, each currently displayed booking must be examined. As the booking system object already has the responsibility for maintaining this set of bookings, it is in a position to iterate directly through the bookings. The user interface checks the details of all the current bookings to see which matches the selection criterion. This is indicated by the ‘getDetails’ message in Figure 5.9. The multiplicity before the message indicates that it will be sent zero or more times, depending on how many currently displayed bookings there are.

In the basic course of events, we are assuming that a booking is successfully selected. As this booking is involved in the later stages of the use case, it is shown separately at the top of the diagram. Role names are used to distinguish the selected booking from the current bookings. A role name does not name an individual object, but describes the role that an object can play in the interaction. Each time this use case is performed, a different object may be the selected booking.

Once the required booking has been selected, and presumably highlighted on the screen in some way, the user invokes the cancel operation. The process of getting confirmation from the user is modelled by a message going from the boundary object back to the user: this may correspond to the display of a confirmation dialogue box, for example. The user is now constrained to respond to this message in some way, and the return message indicates the user’s response.

Once the user’s response is received, the user interface object deletes the selected booking and updates the display before the use case ends. Object deletion is indicated by a message with the ‘destroy’ stereotype: when an object receives such a message, its lifeline is terminated with a large ‘X’, indicating the destruction of the object. Notice that the form that object destruction takes may vary among programming languages and that, in languages with automatic garbage collection, no explicit method call may be required in order to delete an object.

Refine domain model

A new responsibility is implicit in this interaction, namely the responsibility for remembering which is the selected booking. If this is not recorded somewhere, the booking system object will not know which booking to destroy. The sequence diagram in Figure 5.9 assumes that this responsibility is given to the booking system object: as that object is already responsible for maintaining the set of currently displayed bookings, this coheres well with its existing responsibilities. This responsibility needs to be reflected in the evolving class diagram by means of an additional association between the booking system and booking classes, as shown in Figure 5.10. Notice the different multiplicities for these associations: there can be many bookings currently displayed, but at most one of these can be selected. There is an implicit constraint related to these associations, namely that the selected booking must be one of the currently displayed bookings.

Class model

b)

Diagram check bellow for Analysis Model.

Analysis Model

Notation for analysis class stereotypes

Diagram Here

c)

Booking.allInstance->forAll(b1, b2 | ((b1 <> b2) and (b1.table = b2.table)) implies b1.getTime() + 120 < b2.getTIme)

d)

association isAtTable between
	Booking[*] 
	Table[1] 
end

assignTable(table : Table)
	begin
		insert(self, table) into isAtTable
	end

Soil

!new Booking('B')
!B.covers := 5
!new Table('T1')
!openter B assignTable(T1)
!insert (T1, B) into Reserves
!opexit

Precondition

context Booking::assignTable(t : Table)
	pre preTable1 : table.isDefined()
	pre preTable0 : t.places >= self.covers

3.

a)

The idea of defining the architecture of a system in a number of layers is an old one in software engineering and has been applied in many different domains. The motivation for defining layers is to allocate responsibilities to different subsystems and to define clean and simple interfaces between layers. In many ways, this can be viewed as an application of the same principles as in object design, but applied to larger components within the system.

In the architecture that will be described here, one of the fundamental ideas is to make a clear distinction between those parts of the system responsible for maintaining data and those responsible for presenting it to the user. Perhaps the earliest formulation of these ideas was the so-called ‘Model–View–Controller’, or MVC, architecture defined for programs written in the Smalltalk language.

It is often tempting when writing an object-oriented program to create a single class that both holds the data corresponding to some entity and also displays it on the screen. This approach appears to make it easy, for example, to update the display when the data changes: as the same object is responsible for both tasks, when its state changes it can simply update its display to reflect the new state.

However, this approach does not work well when the display consists of a complex presentation of many different objects. In order to display its state sensibly, an object may have to be aware of everything else on the screen, thus significantly increasing the complexity of the class. Further, if the user interface changes, all classes in the system would have to be changed if the responsibility for displaying data is distributed widely. To cope with these problems, the MVC architecture recommends that these responsibilities should be given to different classes, with a model class being responsible for maintaining the data and a view class for displaying it.

Designing a system in accordance with this pattern will have the effect of increasing the number of messages that are passed between objects while the system is running. Whenever a display has to be updated, for example, the view class will have to obtain the latest state of the object from the model class before displaying it.

Nevertheless, the benefits of this approach have been found to make this worthwhile. Among other things, it becomes very easy to define multiple or alternative views of the same data. By using different views, the same application could, for example, support user interfaces based on desktop PCs and on mobile phones. If the maintenance and display of data were defined in the same class, it would be much harder to separate the two and safely update the user interface code.

This principle of making a distinction between the model and the view can be applied at a system-wide level, leading to the identification of two separate layers in the architecture. Classes that maintain the system state and implement the business logic of the application are placed in an application layer, whereas classes concerned with the user interface are placed within a presentation layer.

These two layers are illustrated graphically in Figure 5.1. Each layer is represented by a package, shown as a ‘tabbed folder’ icon containing the package name. Packages in UML are simply groupings of other model elements and are used to define a hierarchical structure on models. A model can be divided into a number of packages, each of which can contain further nested packages, to any level deemed necessary or helpful. The contents of a package can, but need not, be shown inside the icon. If they are not, the package name is written inside the folder icon.

Diagram check bellow for Layered Architecture.

Package diagram

b)

The overwhelming majority of software systems need some way of storing persistent data. This term refers to data that is stored permanently in some way, so that it is not lost when the system is closed down but can be reloaded when required. In the case of the restaurant booking system, it is obviously a system requirement that booking data, for example, should be saved and reloaded on subsequent days when the system is restarted.

There are a variety of possible storage strategies that can be used to provide persistency. A simple approach is simply to write data to disc files, but in the majority of cases a database management system will be used. Although some object-oriented databases are available, the commonest database technology in use is still based on the relational model. In this section, therefore, the design will be based on the assumption that a relational database will be used to provide persistent storage.

Mixing an object-oriented program with a relational database is not a straight-forward task, however, as the two technologies approach data modelling in somewhat incompatible ways. In this section, a simple approach to the problem will be outlined, but a systematic treatment of the issue is beyond the scope of this book. This section assumes a basic knowledge of relational databases and the associated terminology.

The implementation breaks down into two distinct areas. First, it is necessary to design a database schema that will allow the data held in the system’s objects to be stored and retrieved. Second, the code to access the database and to read data from and write data to it must be designed.

The first step in implementing persistent data storage for the booking system is to decide what data needs to be persistent. It is clear that bookings will need to be saved across sessions, as the whole point of the system is to capture and record booking information. Along with this, the table and customer objects that bookings are linked to will also need to be persistent.

By contrast, the data stored in the booking system object, namely the date of the displayed bookings and the set of current bookings, does not need to be stored. When the system is started up, it will usually be of no interest to the user what was displayed last time the system was in use and, if bookings in general are persistent, no irreplaceable data will be lost by not storing the set of current bookings. The remaining class in the application layer, the ‘Restaurant’ class, does not really have any properties of its own, but acts as an interface to the system’s data; provided this data is stored, it does not need to be made persistent.

c)

The previous sections have addressed some of the issues involved in the implementation of simple associations. Specialized forms of associations, such as the qualified associations considered in this section, have properties that suggest that alternative or enhanced implementation strategies are necessary. In these cases, is it worth considering how the designer’s intentions are best preserved in the implementation of the association.

The most common use of such an association is to provide efficient access to objects based on the values of the qualifying attributes. This implies that it will often be necessary to provide some kind of data structure that supports such access in the object to which the qualifier is attached.

For example, we may need to retrieve information about accounts given only an account number. If the bank simply held a pointer to each of its accounts, this operation could be implemented by searching through all the pointers until the matching account was found. This could be very slow, however, and a better approach might be to maintain a lookup table mapping account numbers to accounts. As account numbers can be ordered, for example, this gives the possibility of using much more efficient search techniques.

This kind of structure is relatively easy to implement, the major issue being to decide how to implement the lookup table. In Java, an obvious and straightforward choice is to use a utility class such as java.util.HashTable .

As a unidirectional implementation is being considered, the bank object must have the responsibility of maintaining the association. Operations to add and remove accounts, and to look up an account given its number, could be declared as follows.

Qualifiers, then, can still be handled using the simple model that implements individual links by references. The implementation given above treats a qualified association in much the same way as an association with multiplicity ‘many’. The most significant difference is in the data structure used to hold the multiple pointers at one end of the association. A bidirectional implementation of a qualified association, therefore, does not raise any significantly new problems.

Singleton

NOT ENOUGH!

It is used for caching customers that are persistent.

private static CustomerMapper uniqueInstance;

public static CustomerMapper getInstance() 
{
        if (uniqueInstance == null) 
        {
                uniqueInstance = new CustomerMapper();
	}
	return uniqueInstance;
}

4.

a)

Subclassing/Polymorphism

  • assertions useful with polymorphism, assertions can help keep subclass operations consistent with those of superclass

  • invariants and post-conditions must be true for all subclasses, subclass can strengthen these i.e. make them more restrictive

  • not allowed to strengthen preconditions – because of substitutability, can only weaken a precondition

  • if a subclass strengthened a pre-condition, then a superclass operation could fail when applied to instance of subclass

  • preconditions: best pay-off for least overhead

  • one language to support assertions is Eiffel

In class we have already looked at the role of design by contract (DbC) in software specification. We examined the notions of weakening the pre-condition and strengthening the post-condition in cases of polymorphism and inheritance.

Introduction

Design by Contract (DbC) or Programming by Contract is an approach to designing software. It says that designers should define precise and verifiable interface specifications for software components, with the use of preconditions, postconditions and invariant. These specifications are referred to as "contracts"; in the same way as a business contract entails certain obligations and conditions.

  • developed by Bertrand Meyer, “Bubbles (aka UML boxes) don’t crash”, and forms a central feature of Eiffel
  • uses assertions – assertion is a Boolean statement that should never be false. If so, it indicates a bug
  • assertions usually only checked during debugging/testing
  • three kinds of assertions:
    • postconditions
    • preconditions
    • invariant
  • preconditions and post- conditions are associated with operations

Precondition

  • expresses something about the state of a program that should be true before an operation is executed
  • e.g. a precondition for an integer-square-root operation might be input >= 0 indicates it is an error to invoke square-root on a negative number
  • e.g. pop() should net be called on an empty stack, precondition is s.isEmpty() == false
  • makes explicit that the calling routine is responsible for ensuring that something is true before operation is invoked
  • lack of one may lead to too little or too much checking (duplicate checking code and thus more complicated program)

Postcondition

  • a statement of what things should be like after the execution of an operation
  • e.g. integer-square-root operation result * result <= input < (result+1)*(result+1)
  • expresses what an operation does rather than how it does it
  • separates implementation from interface
  • leads to a stronger definition of an exception.

Invariant

  • an assertion about a class or a method
  • invariant is always true for class instances – meaning whenever object is available for an operation to be invoked on it
  • may be temporarily false during execution of method
  • e.g. Account class balance == sum of transaction amounts
  • e.g. in the above Spec# example, r * r <= x is an invariant of the while loop
  • invariant is added to preconditions and postconditions of public methods

b)

Client

  • Benefit
    • no need to check output values
    • result guaranteed to comply to postcondition
  • Obligation
    • satisfy precondition

Provider

  • Benefit
    • no need to check input values
    • input guaranteed to comply to precondition
  • Obligation
    • satisfy postconditions

Exceptions

Exception occurs when an operation is invoked with its precondition satisfied and is unable to return with its postcondition true

c)

Precision versus Detail

DbC constraint writing in OCL is a form of coding. It is more abstract than 3GL programming as it omits many implementation details that are necessary in a 3GL. But it is still no less precise than 3GL.

Example of an electric motor to illustrate precision versus detail. Spec can very precise while giving no detail as to internals of motor. Abstraction is suppression of irrelevant detail, not vagueness.

Can infer exceptions for operations from DbC constraints. Pre-condition maps directly to an exception to be thrown when the operation is invoked and the precondition is not satisfied.

A lot of tool support now available for checking constraints at runtime, e.g. with Spec#. Generate code for checking constraints. Can specify when in time they are to be checked.

Quality Assurance

DbC can provide a framework for quality assurance (QA). Preconditions, postconditions and invariant represent much of what QA engineer must validate. DbC constraints can be used by code generators to produce test harnesses or for program verification as in Boogie tool with Spec#.

Even when not used automatically, DbC constraints act as precise guides to QA engineers regarding what they must test. Also useful for code reviews or walk-through.

There are other non-functional factors such as scalability and performance, that are not covered by DbC assertions. Can use other UML extension for these.

d)

class Fig1
{
	int ISqrt(int x)
	requires 0 <= x;
	ensures result * result <= x && x < (result + 1) * (result + 1);
	{
		int r = 0;
		while ((r + 1) * (r + 1) <= x)
		invariant r * r <= x;
		{
			r++;
		}
		return r;
	}
}

5.

a)

  • Planning game
  • Small short releases
  • Simple design – simplest possible design that is implementable at the moment.
  • Testing – development is test driven. Unit tests are implemented before code and run continuously. Customers write functional tests.
  • Refactoring
  • Pair programming
  • Collective ownership – anyone can change any part anytime
  • Continuous integration – new piece of code integrated into code-base as soon as it is ready. System integrated and built many times daily. All tests run and have to be passed for change to be accepted.
  • 40-hour week
  • On site customer
  • Open workspace

b)

Not on EXAM!

  • problems solved in software have an inherent complexity, which may be called essential complexity

  • accidental complexity arises from the compromises made that incur technical debt

  • externally imposed ways that software becomes complex

  • payroll example with extra day off in one factory only adds essential complexity as it is part of business rules

  • Accidental complexity

    • Just in time hacking
    • pure plumbing exercises like the first two versions of Enterprise JavaBeans (EJB. A few projects need the extra overhead introduced by these tools, but they do nothing but add complexity to most of the projects that use them.

Three things tend to spawn accidental complexity

  • just-in-time hacks

  • Duplication is the single most insidious diminishing force in software development

    • can arise from copy-and-paste
    • object-relational mapping tool tend to have lots of duplication. Database schema, the XML mapping file, and the backing POJOs have slightly different but overlapping information.
  • The third enabler of accidental complexity is irreversibility. Any decision made that cannot be reversed will eventually lead to some level of accidental complexity

  • Irreversibility affects both architecture and design, although its effects are both more common and more damaging at the architectural level

@cardasac
Copy link
Author

cardasac commented May 9, 2019

USE case realization for display

image

@cardasac
Copy link
Author

cardasac commented May 9, 2019

Cancel booking

image

@cardasac
Copy link
Author

cardasac commented May 9, 2019

Refine class model

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment