[The following write up describes what you will learn in CS2103. Terms in bold are the topics that will be covered. This writeup describes why they are covered and why they are covered in that order. Note that some topics are covered in depth while others are only introduced to show where they fit into the big picture.]
The CS2103 course aims to teach you some of the fundamental Software Engineering principles applicable to non-trivial systems. Our class project is structured as the initial steps of a larger project. We start it as a small program (if you cannot write a small program well, what's the point of learning about big programs?) and progressively increases the size and the complexity. We try to teach SE theory "as they are applied" to your project, but be prepared to learn theory that goes beyond the class project. This is because some of the theory goes beyond the size and complexity of the class project.
At the end of the course you will know some of the fundamental SE theory required to do a small-to-medium size project and have experience in apply them to a small project (0.5 - 5 KLOC) built by a small team (1-5 persons).
First, we want you to get your programming skills warmed up. For this, we give you a small programing activity to do individually. Professional programmers rarely use notepad or simple editors such as Dr Java. That is why we encourage you to use industry standard IDEs such as Eclipse (if you choose Java) or Visual Studio (if you choose C++). The course gives you some help to get started with the IDEs. In addition, we have roped in a couple of your seniors to help you get started with IDEs. Note that the use of IDEs is completely optional.
For a small program, writing it on your own is often simpler than doing it in a team. For bigger programs, there is no choice but to share the workload among a team.
The project we do in this course is a team project. Once you formed a team, we'll ask you to write another slightly more complex program, but working as a team this time. This should give you the first glimpse of complexities inherent to software team projects. The objective of this is to figure out a way to build a system as a team where each team member contributes only a part of it and everyone works in parallel. Resist the temptation to delegate all coding to one person or code one person after the other. Both these approaches do not scale up to larger projects.
As you migrate from single-developer to multi-developer mode, the possibility of failure increases. A bug introduced by any one of the teammates can result in system failure.This is where you should start investing more on verification and validation. For a start, you can start doing some system testing.
As bugs are discovered, you will spend more time debugging and fixing bugs. Debugging is more systematic and efficient if you did it using a proper IDE (one more reason to use IDEs!). Modifying or adding to existing code can sometimes result in regressions. You should use regression testing to avoid regressions.
Regression testing often necessitate test automation, for example, writing automated test driver to system-test the program.
While you doing the team coding exercise to get used to team work, it is also time to get started with the course project. If the domain of the product you plan to build is not well known to you, your first job is to understand it well. You can use domain analysis for this purpose. Complex problem domains are hard to understand. That is why it is helpful have an intuitive way of "looking at" a given domain. The OO paradigm is such a way to look at a domain "as a collection of interacting objects" and to structure a solutions along the same view. At this point, we will refresh your memory about basic OO concepts such as classes, objects, associations, and inheritance and see how you can use them in domain analysis.
During domain analysis you should document the domain as you understood it, which could contain artifacts such as domain models, a glossary of terms, activity diagrams and business process models. These diagrams can be used to verify whether you understood the domain correctly (usually by showing them to domain experts), to ensure all teammates understood the domain the same way, and pass your domain knowledge to those who will continue the project after you. Diagrams are often useful to communicate complex technical details. They can be used as a complement to and sometimes as an alternative to textual explanations. In software projects we commonly use UML as the standard notation for diagrammatic presentations.
Around this time you can brainstorm for features for the product you are going to build. Other than brainstorming, one can use techniques such as analysis of existing products, interviews, user surveys, focus groups, and prototyping to identify what features your product ought to have. Note that you are not expected to complete the full product during by the end of the module; rather, you will complete only some of the functionality. At this point we expect you to decide the *full functionality* of a product under a given broad theme and document them.
The document that captures the requirements of the system is called the software requirement specification (SRS). It specifies requirements of the system. Around this time we will discuss what are the important information that should go into the SRS, how much details should be included in it, and how use cases can be used to capture product requirements based on how users are expected to use it. We will also see how use case do not capture all requirements of a system. For example, often we have to specify non-functional requirements in addition to use cases. Feature lists is another way to present the functionality of a system.
Note that in this project, we ask you to document the project as if you are going to handover the project after the next milestone. This is so that you have a real purpose and a specific target audience when you write project documents. For example, you will document the requirements of the proposed product as if you will handover that document to another team who will build the product based on your document.
Planning the project is another important task. Since you are not going to implement all features, you should decide which features you will implement in what order. Feature dependencies, priorities and effort estimation are some of the things that can help there. Effort estimation will depend greatly on the level of quality you expect to deliver. Therefore, quality and quality assurance should be considered upfront rather than as an afterthought or a by-product. You can use acceptance tests to specify the expected product qualities.
Based on the chosen process, one can create a project plan. This helps in communicating the plan to others as well as to track actual progress against the plan. Gantt charts is one of the popular formats in documenting a plan. However, it can also be a simple text document or a spreadsheet.
An essential part of building a *complex* product while working as a team is avoiding information overload. It is not possible for everyone in the team to know every detail of the system. Some things that can help are software architecture, abstraction, components, APIs, and operation contracts. You will use those techniques to reduce the complexity each of you have to deal with.
Before you can start developing the first increment of the product, you need to decide an architecture for the product. However, it is enough if your architecture of choice is workable; it does not have to be optimal. This is because good architectural decisions require experience, something you do not have yet. However, it should be componetized and high-level responsibility of each component decided so that team members can work in parallel. During the first iteration, your priority is to get something working rather than to perfect the design. You will get a chance to improve this design later on.
During this phase, UML diagrams such class diagrams, sequence diagrams and state machine diagrams can be used as an aid for figuring out the design of the system: for example, to figure out how you are going to code a certain part of the product. They can also be used an aid for communication: for example, when you are discussing merits of two design alternatives. However, there is no need to draw a design diagram for everything before writing code. For less complex parts, it is OK to jump straight into coding. In fact, some consider coding to be a form of detailed design. Therefore, it is acceptable to code and model in tandem. Also note that our course covers only the basic notation needed for drawing the most commonly used diagrams. During early stages of the iteration, do a team discussion to figure out how the components in your system interact to produce the functionality that you will deliver. Some of the UML diagrams you learned will come in handy during this discussion.
As previously stated, we document the project as if we are going to hand it over to a new team after the next milestone. A responsible software engineer should always try to ease the job of colleagues who will use your work products. This almost always include having some documentation to describe internal details of the system. For some parts of the system, the code may be the best form of documentation. For some other parts, all you may need to do is add some comments to the code. However, the best way to convey certain high-level information about the system is by using external documentations. You may use any format or any diagram in those documents. These documents will be judged based on how easy for your peers to use them to takeover the project.
As you get into serious coding, you will be using advanced OO techniques such as inheritance, substitutability, abstract classes/methods,overloading, overriding and polymorphism.
Sometimes you will draw models first and write code to match models. Other times you will write code first and draw models to explain the code. In either case, you should understand the correspondence between models and code. This requires knowing how basic modeling constructs and constraints appear in code.
As you build the product iteratively, you should refine the design/code of the product. Such refining, a common practice in the industry, is called refactoring. Measures to improve the quality of the system at code level include the use of coding standards and approaches such as defensive coding approach or design-by-contract approach.
A responsible team member always try to ensure her parts work as expected, before releasing it to the team. For this, you use developer testing such as unit testing and integration testing. To test your parts independently from the other parts, you need techniques such as drivers and stubs. You can use black-box techniques such as equivalence class analysis and boundary values analysis and glass-box techniques such as coverage based testing to improve the effectiveness and efficiency of your testing.
Refactoring can be used to improve higher-level design too. What makes one design better than another? Criteria such as cohesion, coupling, and dependencies, and principles such as open-close principle can be considered when evaluating designs. Often you can improve the design by using design patterns, which are tried-and-tested solutions to common design problems. Frameworks such as the JUnit testing framework come with ready-to-use code that eliminates the need to code everything from scratch.
We wrap up the course with the project evaluations, a revision of the course materials, and a report on how you applied theory you learned in the course to your project.