Memra

Stepwise refinement, refactoring, and design classes

◈ 5 cards

Refinement as elaboration downward, refactoring as restructuring without behavioural change, the five types of design class, and the four marks of a well-formed one.

Stepwise refinement — elaboration, one level at a time

Stepwise refinement is a top-down design strategy: you begin with a statement of function defined at a high level of abstraction — a procedural abstraction that says what happens and gives no indication of internal workings — and then elaborate it, adding detail at each successive step, until you reach statements a programming language can express. The hierarchy that results is not a plan drawn in advance; it is the residue of the elaboration.

The examinable relationship is that abstraction and refinement are complements, not synonyms. Abstraction lets you specify procedure and data internally while suppressing the need for outsiders to know the low-level details. Refinement is the process that reveals those details as design proceeds. One suppresses, the other discloses; you need both, and the design model is complete when every abstraction has been refined far enough to build and no further.

Refactoring — restructuring without behavioural change

Refactoring is a reorganisation technique that simplifies the design or the code of a component without changing its function or its external behaviour. That last clause is not decoration; it is the definition. Change the structure and the behaviour together and you have done a redesign, or an enhancement, or a bug fix — all legitimate activities, none of them refactoring, and none of them safe to do without re-validating against requirements. Refactoring is the one restructuring you can do with only a regression suite for protection, precisely because the observable behaviour is supposed to be identical before and after.

Refactoring means going back over a design that already works and hunting for what makes it expensive to live with: logic written twice, elements nothing calls any more, algorithms doing more work than the problem needs, data structures that fight the way the code actually reaches them, and any other structural weakness you could put right without changing what the component does. It is heavily associated with agile methods, where it is the mechanism by which a design stays habitable across many increments rather than decaying into whatever the last story left behind. And it carries a real risk that the definition tempts you to forget: the intent is that behaviour does not change, but inadvertent side effects do happen, which is why refactoring is done behind a test suite and why refactoring tools that analyse a change and generate tests capable of detecting behavioural drift are worth their cost.

Design classes, and the four marks of a well-formed one

Analysis produced analysis classes, each describing some element of the problem domain at a relatively high level of abstraction and focused on what is visible to the user. Design produces design classes, which refine those with the technical detail needed to implement them and to create the software infrastructure that supports the business solution. Five types recur:

  • User interface classes — the abstractions the user interacts with, including the metaphors the interface is built from.
  • Business domain classes — refinements of the analysis classes that carry the business concepts.
  • Process classes — the lower-level business abstractions needed to manage the domain classes.
  • Persistent classes — data stores that survive beyond the execution of the software.
  • System classes — the management and control functions that let the software operate and communicate with the outside world.

Each one should then be reviewed against four characteristics of a well-formed design class. Complete and sufficient: it encapsulates everything a knowledgeable reader would expect from its name, and only what is sufficient to achieve its intent — no more, no less. Primitiveness: each method accomplishes one service, and once a service has been implemented the class does not offer a second way to do the same thing. High cohesion: a small, focused set of responsibilities, applied single-mindedly. Low coupling: collaboration with other design classes kept to an acceptable minimum, which is what the Law of Demeter — talk to your immediate collaborators, not to strangers reached through them — expresses as a rule of thumb.

Worked example — refining "return a tool", then refactoring what falls out

Start at the top: return a tool. First refinement: authenticate the member, accept the item into a free locker, record the return, and decide whether anything is owed. Second refinement of that last step: fetch the reservation, compare the return timestamp against the due time plus grace, look up the rate for the tool’s category, multiply by days overdue, cap at the replacement value, and raise a charge if the result is non-zero. That is now close enough to code, and along the way the design classes have named themselves — ReturnScreen (user interface), Reservation and ToolCopy (business domain), FeePolicy (process), ReturnRecord (persistent) and LockerGateway (system).

Now look at what the elaboration actually produced. Reservation has acquired confirm(), release(), isExpired() — and also computeFee(), formatReceipt() and emailMember(), because each was the nearest place to put it when it appeared. That class now performs three functions with only a loose relationship to one another: it has low cohesion, and it fails "complete and sufficient" in the other direction — it contains more than its name promises. The refactoring is to split it into three components each of which is single-minded, moving computeFee() to FeePolicy and the last two to NoticeSender. Note carefully what the refactoring does not do: no fee changes, no message changes, no new capability. Every existing test should pass unaltered — and if one does not, the refactoring has failed by definition, not merely by accident.

Design classesInterfaceReturnScreenDomainReservationProcessFeePolicyPersistentReturnRecordSystemLockerGatewayFive types, one BorrowBox instance each.
The same worked example, sorted. Every class produced by refining "return a tool" lands in exactly one of the five types — a useful completeness check when building a design model for an assignment.
NORMAL ~/memra/learn/comp-410/refinement-refactoring-and-design-classes utf-8 LF