Software Engineering Principles and Practices
1. Software Engineering Fundamentals
1.1 Orientation & Requirements
Software Engineering refers to the tools and processes we use to design, construct, and maintain programs over time. It encompasses the entire software development life cycle:
Write/Refine Requirements → Create Tests → Design → Code → Debug or Redesign until Tests Past → Deploy/Collect feedback
1.2 Requirements Analysis
This phase focuses on making sure we are building the right thing. There are three major dimensions of risk:
- Problems of understanding
- Scope
- Volatility
Functional requirements define what a product must do and what its features and functions are, while non-functional requirements describe the general properties of a system.
- Execution: accessibility, efficiency, performance
- Evolution: testability, maintainability, extensibility
User Stories document requirements from a user’s point of view, specifying what should happen, for whom, and why.
Example: “As a College Administrator, I want a database to keep track of students, the courses they have taken, and the grades they received in those courses, so that I can advise them on their studies.”
Satisfaction Conditions list the capabilities the user expects.
Example: “Add a new student to the database”
2. From Requirements to Code
Test Driven Development (TDD) puts test specification as the critical design activity, understanding that deployment comes when the system passes testing. The act of defining tests requires a deep understanding of the problem and clearly defines what success means.
Satisfaction Conditions → Analyze → Testable Behaviors → Design → Executable Tests → Code → Executing Code → Assemble/Act/Assess
3. Test Adequacy
Why do we test?
- TDD: Does the System Under Test (SUT) satisfy its specification?
- Regression Testing: Did something change from the previous version?
- Acceptance Testing: Does the SUT satisfy its customer?
A “good” test suite should exercise the entire specification, detect bugs that we introduce in the code, and answer: Are we building the right system?
- Statement or line coverage: each statement must be executed at least once.
- Branch coverage: each branch in the control-flow graph must be executed at least once.
- Path coverage: each unique path must be executed at least once.
Mutation testing is a way of judging whether you have written enough tests. The adversary generates a set of “mutants” – buggy versions of a reference solution. Your tests should reject all of the mutants.
4. Code-Level Design Principles
Use design as a way of communicating organization, to achieve non-functional qualities (e.g., readability, maintainability, testability), and to control complexity.
Five General Principles:
- Use Good Names
- Make Your Data Mean Something
- One Method/One Job
- Don’t Repeat Yourself
- Don’t Hardcore Things That Are Likely To Change
5. Interaction-Level Design Principles
A pattern is a summary of a standard solution (or solutions) to a specific class of problems. Patterns help communicate intent and are intended to be flexible.
- The Data-Pull Pattern (Consumer asks producer)
- The Observer or Listener Pattern (Producer tells consumer)
- The Typed-Emitter Pattern (Data source needs to push different kinds of values)
- The Handler-Passing Pattern (When the server creates the client, it passes the client a function to call)
- The Singleton Pattern (Use a first-time through switch and private constructor)
6. Concurrency Patterns in Typescript
Our goal is to mask latency with concurrency, which is achieved using two techniques:
- Cooperative multiprocessing
- Non-blocking IO
Cooperative multiprocessing (Typescript) maintains a pool of processes, called promises. A promise always executes until it is completed (“run-to-completion” semantics) and can be in one of exactly four states (Executing, Ready, Waiting, Terminated).
JS/TS has some primitives for starting a non-blocking computation (e.g., http requests, I/O operations, timers).
The ‘await’ keyword makes your code sequential and ‘Promise.all’ waits for all of the promises in a list to finish.
7. Software Processes and Teams
7.1 Waterfall Software Process Model (~1970)
A systematic, sequential approach with quality assurance at each phase before continuing.
Requirements → Validate → Design → Verify → Implementation → Test → Operations → Retirement
Risk Assumptions: The cost to fix a defect grows exponentially with each development phase.
- Adds process overhead (QA)
- Reduces risk by preventing change and proceeding in stages
- Works well with projects with tremendous uncertainty, with long time-to-market, that need extensive QA of requirements and design, and for which the expense of planning is worth it (e.g., military/defense)
7.2 Agile Planning and Estimation
The Agile model reduces risk by embracing change (~2000).
“We will figure it out as we go.”
- Reduce risk by limiting time on any one stage; then reassess (sprint)
- Agile Practice: Everyone is responsible for quality
- Requirements that become obsolete → Don’t make detailed requirements until you need them
- Elaborate architectural designs never used → Don’t design until you need it
- Code that sits around not integrated and tested in the production environment, eventually discarded → Integrate and test continuously
- Documentation produced per requirements, but never read → Don’t require documentation
Agile principle for effective planning: “You Aren’t Going to Need It” (YAGNI)
Scrum: Daily progress towards product goals.
- What have I done?
- What am I working on?
- What am I stuck on/need help on?
Planning a sprint: Select user stories for the sprint based on priority and value and decompose stories into detailed tasks.
Sprint review: Working demo.
- What did we get done and what value did we deliver?
Sprint retrospective:
- What went well and what could have gone better?
7.3 Teams
Why teams?
- The bus factor: Even if one person can own all of the project, they shouldn’t be relied on.
- Software engineering draws on many skills.
- Agile favors smaller teams because of decreased communication burdens and focusing conversations on relevant topics.
- Code review is a knowledge-sharing opportunity.
- Pair-programming is a knowledge-sharing activity and improves tool diffusion.
HRT pillars of social interaction: Humility, Respect, Trust
– Blameless post-mortem
