Coding Step 3 - High-Level Requirements
Articles in this series:
- Coding Step 0 - Development Environments
- Coding Step 1 - Hello World and Makefiles
- Coding Step 2- Source Control
- Coding Step 3 - High-Level Requirements
- Coding Step 4 - Design
If this series of articles has been light on one thing it's 'coding'. If it's been light on two things the second is 'embedded'. In three articles I haven't gotten past Hello World on a desktop PC. That changes (slowly) with this article. In this article I'll take the first (slow, disciplined) steps toward writing code - specifically, embedded Hello World.
Since the purpose of these articles is to demonstrate a disciplined approach to coding we have to think before we act. In this case, acting is writing code and thinking is writing requirements for that code. I work in the avionics industry where requirements are king and it generally recognizes three types of requirements:
- System-Level Requirements (SLRs) are the highest-level requirements for a standalone subsystem. Usually a standalone subsystem is a box with a connector that will be installed on an aircraft. As a result these requirements can include requirements for software, hardware, physical size, weight and power requirements and even performance under varying environmental conditions such as shock and vibration. Given the lack of strict focus on software, an in-depth discussion of system requirements is not really appropriate for this series of articles (although a standalone article wouldn't be out of the question).
- High-Level Requirements (HLRs) specify the relationship between black-box inputs and outputs of the software. Usually these are text-based requirements. Knowing nothing about what's inside of the black box, they answer the question 'What does the software (black box) do?'
- Low-Level (LLRs) or Design Requirements (DRs) peek inside the black box and thus are written at a gray or white-box level and answer the question 'How does the software implement the high-level requirements?' These requirements can be text-based but it's often more useful to use truth tables, state diagrams, flow charts, etc.
The avionics industry loves the Waterfall Model and Big Design Up Front which focuses on identifying complete and comprehensive requirements before writing any code. This approach has been adopted by other industries much to the chagrin of some. Programmers are generally reluctant about requirements because they prefer to write code, not prose; to work in an IDE, not in Microsoft Word and to compile instead of proofread. They feel less productive when they are taken out of their element and forced to undertake slow, dull and difficult work for what they perceive is little gain.
What's frustrating for me is that even though I'd love to believe that writing requirements is always the "right thing to do", I agree with them: I don't think you should always write requirements for your software. I write plenty of software without requirements: bash scripts, python scripts, makefiles, CRC utilities, etc. Most software only ever solves one problem and then gets put on a shelf to collect dust: no requirements needed. The only times I find it truly beneficial (as an individual developer) to write requirements are when:
- I'm having difficulty thinking conceptually about how to write a piece of software
- I need to expressly limit the scope of an effort
- I want to write tests to verify the functionality of the software
Writing requirements often helps me organize my thoughts. I don't know about you, but C isn't my first language - English is. If I really need to speak fluently and make myself understood I use English. Thus, whenever I can't think straight about new code I just open up Notepad++ and start dumping my brain onto the page. After a while it gets wordy, so I edit, delete, move things around, number and otherwise organize the stream of consciousness. I find that writing prose like this helps for working out high-level details of a program - often by illustrating use cases or a writing step-by-step list of instructions for an algorithm. Simple text doesn't suffice for all situations though. Once you get past high-level aspects of code and start focusing on design you might use state diagrams, truth tables, flowcharts or mathematical equations to specify requirements as well. Writing requirements in this fashion forces you to think differently about coding challenges - this can break down mental blocks, help you get back to writing software and provide you with useful documentation regarding your code.
If you're a professional developer working under a fixed-price contract (ie, 'deliver this in X months, we'll pay you this') limiting the scope of software projects is a necessity because customers will always take more from you if you let them. Without a firm set of universally agreed-to requirements, any developer is susceptible to scope creep. In this context requirements are a contract with your customer: they specify exactly what software will be delivered. If you program professionally and are developing software for others without requirements then you're being taken advantage of. If it's not happening now, it will happen when you ask to be paid. Writing requirements to confine and limit the scope of a programming effort is never optional when writing software for others.
Test-driven development is all the rage recently. The idea is that before you write any code you write the test for the code so you can tell immediately whether the code is correct. But what do you write your tests against? How do you know when to stop writing tests and start writing code? Requirements are the answer to both questions. Requirements which specify exactly what your software does enable you to write tests to verify that behavior. And the question of when to stop testing is simple: stop testing when you've tested all of your requirements. Additionally, requirements (and the tests that come from them) can prove invaluable when doing contract-based work. An agreed-to set of requirements with tests that prove the behavior of the delivered software are an impartial way to prove fulfillment of a contract.
Given that writing requirements can be beneficial (in some situations) you might ask: How do I write them? While there aren't hard and fast rules about writing requirements, in my time working with avionics I've picked up a lot of the conventions used in the industry. These are general guidelines that apply variously to high and low-level requirements - I'll discuss how each rule might apply in either circumstance.
#1 - Requirements should be written plainly and concretely in as common of language as is possible.
This is a restatement of the advice that everyone is given all the time for anything: Keep It Simple (Steve). Don't write this:
The circular buffer Read Function shall, when the valid preconditions are met, exercise the full scope of its abilities by updating its internal state and remanding the required data to the caller.
I did make this requirement up but trust me: I have encountered requirements like this in real life. This is the sort of thing a lazy student turns in if they had already written the code and their teacher was forcing them to have requirements as well. There's nothing right about this 'requirement': it's wordy, has too many clauses, is obsessed with meaningless verbs like 'exercise' and is vague in terms. Any software you tried to write based off of this unintelligible gibberish would itself be unintelligible gibberish. And this doesn't only apply to text requirements: you can make a useless, overly complex state diagram or flowchart if you really try - but please don't. Simplicity in expression of requirements leads to simplicity in expression of code - and simple code is preferable to complex code.
#2 - Requirements should be uniquely identified.
Each requirement gets a unique identifier (UID) - usually something like CB-001. I hope this guidance is uncontroversial because it's really useful to have UIDs. They let you trace requirements to the code that identifies them, indicate what requirements a test is associated with, refer to your requirements in documentation, etc. Generally, there are separate identifiers and numbering for system, high and low-level requirements.
#3 - Requirements should be expressed positively using the word 'shall'.
'Shall' is a bit of a magic word when it comes to requirements. The use of 'shall' separates any random sentence from a requirement. Similar words such as 'should', 'will', or 'may' don't have this magic and sentences that use them aren't requirements. 'Expressed positively' means that you never, ever type the words 'shall not' or 'shall never'. The main reason is one of convention: requirements define what the software does not what it doesn't do. The other reason is that you can't verify a negative requirement: how do I test whether my code doesn't (for example) make me an omelette? For lower-level requirements that aren't text, I can't force you to use the word 'shall' and I don't especially feel like it. If you really want to you can write a text requirement that references the table, flowchart or whatever. For example 'CB-002 - The cbRead function shall implement the logic seen in xxx.'
#4 - Requirements should be testable.
Testing is really the whole point of requirements: you write requirements, write the software against the requirements, then verify it with tests written against the requirements. If you're not closing the development loop in this fashion then you're missing out on a significant benefit of requirements. For a requirement to be testable it must specify controllable inputs and observable outputs. For high-level requirements this is usually black-box I/O. For low-level requirements it could be module or function-level I/O.
#5 - Requirements should be written as outputs in terms of inputs.
This is another guideline I hope is uncontroversial. Whatever software you're working I assure you that your outputs are dependent on your inputs (plus state) and not vice-versa. The only time I've seen issues with this is when text requirements are written by non-English speakers where their native language has interesting sentence order. Even then, it's just language confusion and not disagreement about this principle.
So, with all of this in mind what sort of software are we going to write and what requirements do we need? Once again I'll demonstrate my lack of imagination and ambition and propose that our next goal should be to write Hello World - but this time, for an embedded system. Given that, I propose the following one high-level requirement for embedded Hello World:
EHW-001 - The software shall transmit the text "Hello World!" over a serial connection within 3s of startup.So the question is: does this meet the criteria I just described above? Let's do a quick requirements review:
#1 - Is this requirement written plainly and concretely in as common of language as possible?
Basically, this question comes down to whether or not you can understand the requirement. Does it use verbs with poorly-defined meanings such as 'process', 'parse', 'store', etc. ? Does it use terms that aren't defined? Are there any phrases that lack sufficient definition?
If I looked at this requirement I'd start to pick it apart this way:
- 'text' - I know there are many different ways text can be represented, the most fundamental of which is ASCII or Unicode. But this not a beneficial argument to bring up a this stage: the choice of a particular encoding scheme is more of a design-level detail than a high-level one. The meaning of 'text' here is clear enough: you should be able to read it and it should say 'Hello World!'
- 'serial connection' - Yes, this is not exactly specific, but keep in mind that high-level requirements like this will usually be paired with system-level requirements which actually will specify things like 'The system shall provide an RS-232 serial port capable of 115,200 bps asynchronous communication.' The vagueness here about the port itself is because it's more of a hardware function than a software one. Once we have finished defining the functions of the software we can determine what kind of serial port will support those functions.
- 'within 3s' - If you're particularly picky maybe you'd ask for a timing diagram showing exactly what 'within' means but for most people it will be clear: at the end of a 3s period beginning at 'startup', the text "Hello World!" will have been transmitted over the serial port.
- 'startup' - This is a legitimate issue with the requirement: 'startup' is not defined. True, if you're a seasoned engineer you might assume that you understand the term and not give it a second thought. But in the requirements game you only win if everyone who reads the requirement comes to the same understanding. So, one old timer might read it as this:
EHW-001 - The software shall transmit the text "Hello World!" over a serial connection within 3s of application of power.
Where another engineer might see this:
EHW-001 - The software shall transmit the text "Hello World!" over a serial connection within 3s of a software reset.
The problem is that they're both right to a certain extent but more to the point: the person that wrote the requirement is wrong because the term 'startup' is not well-defined. It would be better to write the requirement this way:
EHW-001 - The software shall transmit the text "Hello World!" over a serial connection within 3s of application of power or a software reset.
There - no more confusion! On to the next question:
#2 - Is the requirement uniquely identified?
This is a gimmie: yes, it has an identifier (EHW-001) and yes, it is unique.
#3 - Is the requirement positively expressed and does it use the word 'shall'?
Another gimmie: yes, it uses 'shall' and does not use 'shall not' or 'shall never'.
#4 - Is the requirement testable?
Tests require controllable inputs and observable outputs. In this case, the only input is time (ie, 'within 3s') and the output is text on a serial port. True, we can't control time but time will move forward on its own, consistently, and does not (so we believe) ever move backwards so we'll count that as controllable. The output should be easily observable given a proper serial port and software, so it passes on both counts.
#5 - Is the requirement written as outputs in terms of inputs?
This is yet another easy question: yes, it is. For reference, if it were written as inputs in terms of outputs it would read like this:
EHW-001 - When the text "Hello World!" is transmitted over the serial port, at most 3s will have passed since application of power or software reset.
I hope everyone agrees this is a very odd way to write requirements and should be avoided.
So, our new and improved requirement is:
EHW-001 - The software shall transmit the text "Hello World!" over a serial connection within 3s of application of power or a software reset.That's your first high-level software requirement! So, are we ready to write code yet? Sadly, no: there's a lot more definition of the system to perform. The next article will delve into the nitty-gritty of how the actual serial transmission is accomplished by taking a look at how to design code and write low-level functional requirements.
One thing I would have liked to see is your thoughts on actual implementation. You went in-depth with the requirements (i.e using 'shall' to represent requirements). How about testing? I know its maybe out of scope for this article specifically, doesn't change the fact that it would have been nice to hear/read your thoughts on it.
To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.
Please login (on the right) if you already have an account on this platform.
Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: