3.2.1 - Formative project skills: decomposing a solution

3.2.1 - Formative project skills: decomposing a solution

Decomposition turns a large problem into parts whose responsibilities and connections can be understood. You will practise choosing useful boundaries, specifying the information between parts and justifying how the parts cover the problem. All examples and activities are formative project-skills practice and do not supply or certify an assessed H446/03 solution.

Split by computational responsibility

Decomposition breaks a problem into smaller parts that can be designed, developed and checked more manageably.

The parts must still be suitable for computational solutions. Splitting a report into “analysis, design and evaluation” organises documentation but does not decompose the program's problem. Splitting source into arbitrary blocks of equal length is also unhelpful: each block may contain several unrelated responsibilities.

Consider a fictional seed-swap service that records offered seed packets and matches a request to a suitable available packet. A useful first decomposition separates recording offers, validating requests, finding eligible offers, choosing between eligible offers, confirming the allocation and presenting the result. Each part changes or establishes something specific. The interface gathers a request, while the matching part applies the seed and availability rules. This allows those rules to be checked without clicking through every interface action.

Describe responsibilities as operations rather than vague nouns. “Database” identifies a tool or store; “load available offer records and save confirmed allocations” identifies work. “GUI” identifies a surface; “collect a request and display a clear result” identifies behaviour. These responsibilities can later be implemented in suitable modules, classes or subroutines; decomposition is reasoning about the problem before committing to a particular code layout.

A part may need further decomposition. Recording an offer could include validating fields and storing the accepted record. Stop when the responsibilities are clear enough to design and test. There is no universal required number of modules. Too many tiny parts can add interfaces without improving understanding; one enormous “do everything” part hides important decisions.

Define what passes between the parts

A useful split needs an interface: an agreed description of the data and results exchanged between parts.

Here, an interface is a contract between computational parts, not necessarily a graphical screen. State the input's meaning, the expected conditions, the output's meaning and relevant failure outcomes. A module name such as find_offer does not tell its caller whether it returns one record, a list, an identifier or an error.

For the seed-swap practice problem, one possible interface is:

PartReceivesProducesBoundary
Validate requestEntered seed type and quantityA valid request or specific field errorsDoes not alter offer records
Find eligible offersValid request and current offer recordsEligible offer identifiersDoes not allocate packets
Choose offerEligible identifiers and the agreed preference dataOne chosen identifier or a no-match resultDoes not pretend a match exists when the list is empty
Confirm allocationChosen identifier and current offer recordsConfirmation or a no-longer-available resultChanges records only when confirmation succeeds

This is a small worked contract, not a complete implementation. Its usefulness comes from distinguishing checking, choosing and changing state. “No match” is a valid outcome for a legitimate request, not necessarily invalid input. The interface must allow that outcome so the display can tell the user what happened.

Specify a consistent representation. If one part uses seed-type IDs and another expects display names, either agree a common representation or provide a deliberate conversion. Otherwise independently plausible modules can fail when connected. A precondition describes what must hold before an operation; a postcondition describes what it establishes afterwards. For example, a matching operation may require a valid request and promise a list containing only eligible offers.

Passing relevant data explicitly makes dependencies easier to see and reduces hidden reliance on shared state. Where shared storage is necessary, describe which part reads or changes it. The caller should not have to guess whether an apparently harmless lookup also edits availability.

Continue this lesson.

You’re previewing NovaLearn without an account. Create a free account to read the rest.

Create free account