Course page: Requirements and Architecture (1) · Slides · Video

Once code, tests, and prototypes can all be generated quickly by AI, the most expensive part of software development is no longer just writing the program out. What the different participants actually want to solve, what counts as correct behavior, and which decisions have to be redone after a requirement changes all determine whether the generated system can keep evolving.
Software engineering studies how Intention enters Specification and is then landed as Implementation, and how the links among the three are kept during maintenance. Software is a logical artifact, and a slight inconsistency can make it fail; an error in the implementation can also travel back along dependencies to expose problems in requirements and design. AI lowers the cost of implementation, and it also spreads unexamined assumptions faster. Requirements, product design, and architecture therefore become the levers that decide where software goes.
These questions are also hard to compare in a unified experiment the way algorithmic performance is: defect prediction can answer which code is more likely to go wrong. It cannot directly judge what the client really needs, whether a product is usable, or whether a given architecture will still be suitable a few years later. The harder a decision is to quantify, the more its assumptions, feedback, and actual results need to be kept and checked repeatedly.
1. Requirements are not a list of features
The registrar system brings the hard problems of requirements engineering together. Students care about course selection and graduation, teachers need to schedule classes and record grades, and academic affairs staff execute the curriculum plan and handle exceptions; the goals of these roles meet at quotas, prerequisites, and graduation review. Each participant may state their own case clearly, yet someone still has to decide how the system should act when the goals conflict.
Imagine a curriculum plan that requires at least 160 credits, with general education, major requirements, electives, and practical credits satisfied separately. A mathematics requirement can be met by Calculus A, or by Calculus B plus a bridge course; a student with a deferred exam can select a follow-up course temporarily, and the selection has to be revoked if the exam is still not passed before the course starts; two approved exchange courses can count jointly toward one required course, and cannot be counted twice toward elective credits. Changing majors, retaking courses, make-up exams, course substitutions, and manual waivers interleave these rules further. Each rule looks simple on its own; the difficulty is that they take effect at the same time for a particular student in a particular semester.
The boundaries of a field affect the whole system as well. If a student's absence count is changed from 0 to -1, several course-selection pages that depend on this record may fail. A number that looks local may already be a premise many features share; patching the broken pages is not a substitute for understanding what the data means, what range of values is valid, and how it is shared.
The system also has to explain the exception itself: who approved the waiver, which version of the curriculum plan it rests on, whom it applies to, and what the reason was. If the system does not express these business facts, engineers may be able to solve the immediate problem only by editing the database directly. The result may be correct for the moment, and afterwards it is hard to tell whether this was a formal approval or a firefighting operation that left no evidence behind.
Having AI investigate the current process and organize the requirements catalog, roles, terminology, acceptance scenarios, and traceability links produces a starting point that can be discussed quickly. Formal requirement numbers help connect business goals, requirement items, and tests, and they also help a team divide work and check progress. A complete document does not mean the participants have reached agreement, and a large number of requirements does not mean the product's value has been validated. A tool can organize the rules it has observed, and it cannot settle conflicting goals on behalf of the business parties.
A specification also covers many behaviors that look minor: how the current semester is switched, what is shown first when a user holds several roles, during which time window a button is available, and what reason is given when an operation is refused. Writing these into observable acceptance scenarios is what allows the team to check whether the different roles share one understanding; guesses that have not been confirmed should be kept as open items. Generating the implementation, the tests, and the demo interface from the same incomplete specification only repeats the same omission across several artifacts.
2. An MVP finds requirements and leaves technical debt behind
The value of a minimum viable product is that the least amount of implementation gets a result in front of users quickly, so they can see it, operate it, and criticize it. A prototype of a class schedule, a course-selection entry point, or a graduation review can surface requirements that were never spoken, such as whether an exchange course needs to be recognized. A prototype moves the discussion from abstract description to real use, and the feedback tends to be more concrete than adding more pages to a long document.
A prototype that passes the normal path does not mean the system has understood the exceptions in reality. When a requirement changes, the edit may travel from the interface all the way through course mapping, credit calculation, the transcript, review logic, and tests. If "one exchange course substitutes for one local course" changes to "two exchange courses count jointly toward one required course", the team still has to answer whether old decisions are recalculated and whether exceptions already approved remain in effect.
Every early decision leaves a constraint for the future. AI may quickly pick a database, a frontend and backend structure, an Excel version, or a data layout; once these choices enter the code, the tests, and the context of an ongoing conversation, every later requirement takes them as a premise. The more unverified decisions there are, the harder it is to predict the scope of rework. An MVP should verify the most important value first and keep unverified assumptions visible, rather than implementing every feature anyone can imagine at once.
Requirements can never be fully stated in one pass, and they change with the organization, the policy, and the experience of use. Rapid prototyping is not a universal answer, and neither is fixing early understanding into a complete specification waterfall-style. Change is unavoidable, and what engineering design has to do is give change a place to go, keeping it inside a small number of clear boundaries.
3. Architecture starts with a few lines of code
Architecture is not a box-and-arrow diagram reserved for large systems, and it is not the same as choosing microservices, a database, and a tech stack. A small program split into the stages of reading input, parsing, processing, and output is already deciding responsibilities and how data flows. If every step quietly modifies the same set of global variables, the surface function boundaries do not stop change from spreading; a real boundary also has to state who owns the state, who may depend on whom, and who handles errors.
Command-line argument parsing is an architectural choice too. Handling arguments with a chain of string comparisons scatters short options, long options, and format changes through the business code; centralizing parsing in a standard parser and then converting the result into an explicit argument state confines one kind of complexity inside a boundary. The functions, types, scopes, and call stack of a programming language can all express constraints and responsibilities.
Architecture decides where complexity sits, and which parts a requirement change affects. Splitting code into more functions or files does not automatically produce good architecture; the question is whether stable interfaces, state ownership, and dependencies are clear. Every abstraction should be able to state which kind of change it isolates and what maintenance responsibility it adds.
4. The Towers of Hanoi show that representation changes the difficulty
Non-recursive Towers of Hanoi can be expressed in several ways. The first uses the mathematical pattern of the optimal move sequence directly: which disk moves on which step follows from the number of trailing zeros in the binary form of the step index, and each disk's direction of movement is fixed as well. The second pushes the pending tasks produced by the recursive decomposition onto a stack and pops them in order for execution. The third simulates the function calls, explicitly storing the parameters of each call level and the position the program has reached.
The three approaches place complexity in different places: a mathematical pattern, task decomposition, or program execution state. The first is short, and it requires the pattern to be understood and verified; the task stack stays closer to the recursion itself; simulating the call stack turns the operational semantics into a state machine. The choice depends on what has to be explained, what has to be verified, and which representation lets a person grasp the problem. A short or efficient implementation is not necessarily the easiest to maintain, teach, or modify.
Architectural ability also comes from seeing the problem outside the current code: algorithms, data structures, formal semantics, and how other people use the system all change how the problem is represented. Adding conditions inside an existing representation usually adds branches; with a suitable model, the complexity can become manageable.
5. The Excel QR code needs a compiler
Typing a string into an Excel cell and letting the spreadsheet formulas generate the matching QR code is a requirement with a clear goal and checkable results. The QR code encoding algorithm is easy to express in an ordinary programming language, and forcing it into worksheet formulas produces long, fragile expressions. Once the formulas depend on a particular Excel version, cell addresses, or layout, changes such as version compatibility, moving the output location, or adding an input can force a rewrite of the whole block of formulas.
Seen from outside the problem, this is a matter of translating one representation into another rather than simply writing more formulas. An intermediate representation (IR) can be designed between the algorithm and Excel, with nodes for input, constants, XOR, table lookup, and conditional selection, and edges recording data dependencies. AI can describe the QR code computation in this language; the same intermediate representation can generate a Python program for checking and Excel formulas for delivery.
This structure hands changes to different parts: the algorithm describes the encoding logic, input binding handles cell positions, the layout module decides where intermediate results go, and the target backend generates formulas for a particular version. When the Excel version changes later, mainly the generation of the corresponding operators changes, and the algorithm does not have to be rewritten from scratch. A shared intermediate representation also supports optimizations such as constant folding and merging repeated computations.
Verification still has to account for errors the two sides share: if Python and Excel are generated from the same faulty logic, the two results agreeing cannot prove the QR code is correct. An independent implementation or a QR code decoder can verify the content further; when comparing matrices, the encoding segments, error correction level, and mask options also have to be fixed. Architecture gives verification and change a defined place, and it does not make correctness automatic.
6. Storing only the current state loses where decisions came from
A traditional CRUD system usually maintains the current state: students, courses, grades, course-selection records, and the "meets graduation requirements" value computed from them. Database normalization reduces redundancy and avoids update anomalies; for simple information maintenance, CRUD is direct, clear, and often sufficient.
The difficulty is that many states are conclusions drawn by computation. A student being "allowed to graduate" may rest on course grades, the version of the curriculum plan, course substitution approvals, and manual waivers. Storing only a boolean cannot explain why it holds, and cannot tell which reviews need rechecking after a grade is corrected. When a policy has already been approved in reality, a classroom is under repair, or an organization changes, the system may not record these facts in time, and manual patches gradually build up data that cannot be explained.
A department renaming shows the conflict in historical semantics. If "Department of Computer Science" is changed directly to "School of Computer Science", past transcripts may be displayed as belonging to the school period; if a new organization ID is created, the succession between students, courses, permissions, and historical records has to be migrated as well. How the name changes is one question; the others are whether the organization's identity continues, when attributes take effect, and whether old records are queried by the structure of their time or by today's structure. Code can be rewritten, and history that has already been lost cannot be reliably restored afterwards.
7. Event Sourcing stores what happened
Event Sourcing stores business events in order rather than overwriting the current state, then applies the events to an initial state to obtain the current view at a point in time. A ledger records income and expenses one entry at a time, and the balance is only the result computed from those records; in the same way, the system can store facts such as "the course-selection application was accepted", "the course substitution was approved", and "the grade was recorded", then project them separately into a student's class schedule, a teacher's roster, or a graduation review result.
Keeping events gives the system a chance to answer what happened in the past and what a decision rested on, and to rebuild the current view from history. Event names alone are not enough. Explaining a particular graduation review also requires the inputs of that time, the rule version, snapshots, and the approval basis; correcting an error should append a traceable correction rather than quietly erasing the original history. Business rules and event formats evolve as well, and replaying history has to avoid repeating external side effects such as sending notifications or charging money.
Time carries more than one meaning too: a waiver may take effect on June 1 and be entered into the system only on June 3. Judging whether it was in effect on June 2 and explaining why the system refused a graduation review on June 2 need the business effective time and the system recording time respectively. Compressing the two into one timestamp loses the information needed to explain historical decisions.
Event Sourcing brings responsibilities around storage, version evolution, replay cost, and concurrency control. The current view can be made cheaper to query through incremental updates or snapshots; when only one seat is left, two course-selection applications that arrive at the same time still have to compete atomically, and appending events does not resolve a concurrency conflict by itself. For simple data that needs no tracing or auditing, CRUD may fit better. Architecture should be chosen according to the business history that really has to be explained, rather than adopting a complex design for the sake of a pattern name.
8. CQRS and DDD solve different problems
CQRS (Command Query Responsibility Segregation) separates the write model that accepts business operations from the read model that provides query results. The write model checks prerequisites, quotas, and time conflicts; a student's class schedule, a teacher's roll book, and academic affairs statistics can each use a read view suited to them. A read view can be precomputed or redundant data, and it can also be updated later; with asynchronous updates, the interface has to state clearly when a command counts as successful, so users do not submit again because the query result has not refreshed yet.
CQRS can be combined with Event Sourcing, and it can also be used independently of it. It emphasizes the separate responsibilities of writing and querying, and does not require all history to be stored as events. DDD (Domain-Driven Design) starts from business concepts and model boundaries: within different bounded contexts such as course selection, finance, and graduation review, the same "student" or "pass" may carry different meanings. Compressing these differences into one generic field makes it hard for the system to explain that a course was passed and why it still cannot count toward a particular graduation requirement.
These methods are not buzzwords that can be stacked freely. Business boundaries, query loads, history tracing, and consistency requirements differ, and so do the structures they call for. A simple scenario can keep direct CRUD; a complex one can first record rule versions and inputs for key decisions, then build explainable projections and review processes step by step.
Leaving room for the next change
From command-line parsing and the Towers of Hanoi to the registrar system, architecture always concerns how to represent the problem, isolate change, and keep the evidence that is needed. Before coding starts, settle who owns the key business decisions, who owns the data, which relationships have to be traced, and which states have to be consistent immediately; keeping unverified assumptions changeable is also worth more than committing early to complex infrastructure.
Reworking an existing system can proceed along one important business chain, filling in facts, rule versions, and approval evidence step by step, comparing new and old results in parallel, explaining the high-risk cases first, and then switching the authoritative write entry point. The process has to respect the history of the real data: record what the old records can prove, and evidence that is already missing cannot be disguised as never lost.
AI can generate more implementations and help organize requirements and compare options; whether the software survives the next change still depends on whether people see the business meaning, the dependencies, and the failure boundaries clearly. The easier code becomes to generate, the more carefully the few abstractions and decisions with long reach have to be chosen.
