If you get stuck, be sure to view the Instructional Videos.
For Lab0 below (Calendar example) view accompanying Video here
Precondition: Study how to to use the EiffelStudio IDE, add the ESpec library, change the root class, add clusters and classes, and write tests.
If you have met the above preconditions, you should be able to start a new Eiffel project from scratch. Start Eiffelstudio, and setup a new project “calendar” as shown in the steps below:
Create a new calendar
project at the terminal: Eifflel 18.11 Void Safe Starter Project:
red> eiffel-new New Eiffel void-safe project name: calendar red> ls calendar
Check your directory structure to see that the file system mirrors the cluster hierarchy.
calendar ├── calendar.ecf ├── model │ └── calendar.e ├── root │ └── application.e └── tests └── test.e
The new project is in the folder calendar
.
estudio18.11 calendar/calendar.ecf&
Also, take a look at the Settings and the XML/ECF file to see the Void safe settings.
To add a new cluster or class see FAQ: Adding a new cluster/class
The start of a Calendar class might be as follows:
Note that the query has a precondition, and calls another query to calculate the leap year:
The leap year calculation also has a postcondition.
Also note the indexing clause. Apply the self-documentation principle by giving meaningful names, preconditions, postconditions, invariants etc.
Note: the implementation of the leap year query may not be correct. Does it satisfy the postcondition?
In the Gregorian calendar (established in 1582) we add a Leap Day on February 29, almost every four years. Three criteria must be taken into account: The year can be evenly divided by 4; If the year can be evenly divided by 100, it is NOT a leap year, unless, the year is also evenly divisible by 400. Then it is a leap year. This means that in the Gregorian calendar, the years 2000 and 2400 are leap years, while 1800, 1900, 2100, 2200, 2300 and 2500 are NOT leap years.
Instead of the informal English description, the postcondition is a precise mathematical specification of the leap-year query.
Try the following tests
Test t0
is a Boolean query whereas test t1
is a command (a violation case). Make sure you know the difference.
Note that a boolean test must terminate with Result true, but you can also add some asserts along the way. In addition to the comment
, you can also apply additional documentation via sub_comment
.
The Root class should look as follows
The root class inherits from ES_SUITE. (You can also let the root class inherit from ES_TEST, and add the tests in the root class). We can then add further classes with other tests.
Ensure that you get the above feedback.
Question: How many tests must you write to ensure than you are calculating the leap year correctly?
Here is another question. Suppose wee add another violation test t2:
Why?
We wish to add additional features such as advance the date to tomorrow and next week, and change the date to yesterday and last week. We may also want to calculate the number of days between two dates.
The user will have to keep adding arguments to commands tomorrow(a_year, a_month, a_day:INTEGER)
, yesterday (a_year, a_month, a_day:INTEGER)
etc.
Is there a better design? One that avoids all those arguments evert time we wish to change the date?
How about the following:
Note that the invariant constraints dates to the potentially allowable range, but a feature is_valid_date
is still needed to check that the actual date is allowable given leap years etc.
When tests fail, it is essential to be able to use the debugger to track down the error.
This is part of a series of instructional videos: Eiffel instructional videos
Wikipedia defines abstraction as follows: “In software engineering and computer science, abstraction is a technique for arranging complexity of computer systems. It works by establishing a level of complexity on which a person interacts with the system, suppressing the more complex details below the current level. The programmer works with an idealized interface (usually well defined) and can add additional levels of functionality that would otherwise be too complex to handle. For example, a programmer writing code that involves numerical operations may not be interested in the way numbers are represented in the underlying hardware (e.g. whether they're 16 bit or 32 bit integers), and where those details have been suppressed it can be said that they were abstracted away, leaving simply numbers with which the programmer can work. In addition, a task of sending an email message across continents would be extremely complex if the programmer had to start with a piece of fiber optic cable and basic hardware components. By using layers of complexity that have been created to abstract away the physical cables and network layout, and presenting the programmer with a virtual data channel, this task is manageable.”
Beck's map of the London underground is a wonderful example of abstraction, in which irrelevant details have been suppressed and only the relevant information to use the underground is described:
How can we write the contracts for additional features such as tomorrow
, yesterday
and the number of days between any two dates? What about if awe want to convert one calendar date (say the Gregorian) to a Julian date, or a Persian date?
This is where a good abstraction can greatly simplify the code and its specification.
In Calendrical Calculations, Nachum Dershowitz et. al (Cambridge), provide an abstract model of dates as a sequence of integers
..., -3, -2, -1, 0,, 1, 2, 3 ,..
When introducing any new calendar (say the ISO calendar) all that we need is a function to convert from that calendar to the fixed days and vice versa. Here is the abstraction function for the Georgian calendar:
g2fd
is an abstraction function. We can now write the specification for the command tomorrow
as
year: INTEGER month: INTEGER day: INTEGER tomorrow do ... ensure g2fd(year, month, day) = old g2fd(year, month, day) + 1 end
Here is a test case:
t3: BOOLEAN local c1, c2: CALENDAR do comment("t3: test tomorrow") create c1.make (2016, 12, 31) create c2.make (2017, 1, 1) Result := c1.is_valid_date check Result end c1.tomorrow Result := c1.year=2017 and c1.month=1 and c1.day =1 check Result end Result := not (c1 = c2) and (c1 ~ c2) create c1.make (1900, 2, 28) c1.tomorrow Result := c1.year=1900 and c1.month=3 and c1.day =1 check Result end c1.yesterday Result := c1.year=1900 and c1.month=2 and c1.day =28 end
With a sequence of integers as a data abstraction, converting from one calendar to another and specifying calendar features becomes much simpler.
See “Building Maintainable Software: Ten Guidelines for Future-Proof Code”, by Joost Visser, O-Reilley, 2016.
Difficult-to-maintain source code is a big problem in software development leading to costly delays and defects. In addition, it is usually frustrating working with someone else's code. The mark of a professional is the ability to develop code that others can understand and maintain. The authors provide the following guidelines for building maintainable software: