NivaarExam PrepOfficial exam papers ↗

25-Comp-A6 Software Engineering · December 2015

Question 4 of 8: Software Reuse and Portability

Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)

Notes on this paper

National Exams — December 2015 — 98-Comp-A6 Software Engineering. Three-hour, closed-book exam, no calculator permitted. Format: eight questions, candidates answer any five of the eight (all questions equal weight — each of the five counted questions is worth 20%; only the first five questions as they appear in the answer book are marked). All eight questions are solved below for completeness.

Reference texts: Sommerville, Software Engineering (10th ed., Pearson) — software process models, object-oriented and function-oriented design, software testing, requirements engineering, dependability and critical systems, configuration management, project/risk management; Pressman, Software Engineering: A Practitioner's Approach (9th ed.) — supplementary process and testing coverage; Leveson, Safeware: System Safety and Computers — hazard analysis and fault tree analysis for safety-critical software (applied to the Question 7 medical-device case).

Question 4: Software Reuse and Portability (a) 10, (b) 10 — 20 marks

Question text not reproduced: the examination questions are © Engineers and Geoscientists BC. Open the official past paper (linked at the top of this page) to read the question, then follow the worked solution below.

(a) Information-Hiding and Inheritance for Reuse

Information hiding means a component exposes only a well-defined public interface and conceals its internal data representation and algorithms behind it, so that client code can depend on the interface's contract without knowing (or being able to depend on) how it is implemented. For reuse, its advantage is robustness to change: the component's internals can be re-implemented — for performance, for a new platform, or to fix a defect — without breaking any client, so a well-hidden component can be reused across many contexts as a stable black box. Its disadvantage is inflexibility at the margins: if a new context needs slightly different internal behaviour that the interface was never designed to expose, the component often cannot be adapted at all without modifying it directly, forcing either a fork or an awkward wrapper.

Inheritance lets a new class reuse an existing class's implementation and interface by deriving from it and overriding or adding only what differs. Its advantage is speed of reuse: a large body of correct, tested behaviour is obtained essentially for free, and polymorphism lets client code manipulate the base type while actually running the derived behaviour. Its disadvantage is the coupling it creates between subclass and superclass — the well-known "fragile base class" problem, where a change to the superclass's internals (even one that preserves its public interface) can silently break subclasses that depended on some undocumented aspect of the original implementation, and where deep inheritance hierarchies become difficult to understand and to modify safely as a codebase grows. In practice, information hiding is usually the safer default for reuse across module or team boundaries, while inheritance is most effective when reused within a single, well-understood hierarchy under one team's control.

(b) A Portable Calendar/Clock Abstract Data Type

Portability from an 8-bit microcontroller to a 64-bit special-purpose processor is achieved by hiding the actual bit-width and internal representation of time behind an abstract interface expressed only in terms of calendar/clock concepts (year, month, day, hour, minute, second) — never in terms of a specific integer width, register layout, or hardware timer. The client code is written once against this interface; only the small implementation file behind it changes per target.

/* calendar_clock.h -- portable, machine-independent interface */
typedef struct CalendarClock CalendarClock;   /* opaque handle: internal
                                                  representation is never
                                                  exposed to client code   */

CalendarClock *cc_create(void);
void  cc_destroy(CalendarClock *cc);

void  cc_set(CalendarClock *cc, int year, int month, int day,
             int hour, int minute, int second);
void  cc_tick(CalendarClock *cc, int elapsed_seconds);   /* advance time */

int   cc_year(const CalendarClock *cc);
int   cc_month(const CalendarClock *cc);
int   cc_day(const CalendarClock *cc);
int   cc_hour(const CalendarClock *cc);
int   cc_minute(const CalendarClock *cc);
int   cc_second(const CalendarClock *cc);

Two implementation files satisfy this same interface on very different hardware. On the 64-bit target, the whole struct can simply store a 64-bit signed "seconds since epoch" count, since 64-bit arithmetic is native and cheap:

/* calendar_clock_64.c -- 64-bit target */
struct CalendarClock { int64_t seconds_since_epoch; };
/* cc_tick just adds elapsed_seconds to the 64-bit counter;
   cc_year/month/day/etc. decode it with ordinary civil-calendar math. */

On the 8-bit target, which has no native 64-bit (or even 32-bit) arithmetic unit, the same interface is satisfied by storing the six calendar/clock fields directly as small integers and doing carry propagation by hand in cc_tick (seconds roll into minutes, minutes into hours, and so on, with a small look-up table for days-per-month and a leap-year test for February):

/* calendar_clock_8bit.c -- 8-bit target, no wide-integer arithmetic */
struct CalendarClock {
    unsigned char second, minute, hour, day, month;
    unsigned int  year;             /* only field wider than 8 bits */
};
/* cc_tick increments second, propagating carries into minute, hour, day
   (using a days-per-month table and a leap-year check), month, year --
   entirely with 8-bit/16-bit arithmetic, no 32- or 64-bit operations. */

Because every client of the ADT calls only cc_set, cc_tick, and the six accessor functions, porting the calendar/clock to a new machine is entirely a matter of swapping the implementation file for one matching that machine's natural arithmetic width; no client code anywhere in the system needs to change.

Check
Assumes the 8-bit target's year field is allowed to be wider than 8 bits (a common exception, since a single 8-bit byte cannot represent a 4-digit year) — if the target absolutely cannot spare more than 8 bits anywhere, a smaller epoch-relative year offset would be substituted, again invisibly to client code.