Day by day, software programmers make all kinds of little design decisions. Software architects may determine the high-level organization of a system and its key abstractions - but high quality software depends on those implementing it making good choices too. Individually these choices are small, however put together they have a large impact on software quality.
Its not easy to make all of these small, but significant, design choices - in no small part because there are so many factors. How does modern hardware actually work? What does the CLR do with .Net bytecode? Does encapsulation and abstraction have a cost? How does the behavior of the garbage collector impact programs? What data structures are appropriate in what situations? How can we actually use OO effectively? When are mutable and immutable designs preferable? How can software be extensible and composable? How should dates and times be handled?
There are courses that teach you software architecture at a high level, others that teach you how to use a programming language and its libraries effectively, and yet more covering specific frameworks or tools. Instead, this course takes on the gritty, tricky, day-to-day choices that developers are expected to make, as they go about the heroic task of transforming usually ill-defined and ever-changing needs into working software that solves problems. It juggles a dozen topics. Because thats exactly what good software developers have to do.
Youre already a decent C# programmer - but you know theres more to software than writing the code. You know you have to make dozens of little decisions in your day to day work: about performance trade-offs, data structures, and OO design. And youd like to deepen your knowledge in a range of areas to help you make better decisions.
This course assumes that participants are able to follow and write code using modern C# language features, including Linq and lambda expressions. Taking our C# Intermediate course is suitable preparation, otherwise you should have at least the skills covered by the C# Intermediate course.
The environment we build for
Its important to understand what were running on, and what were building in terms of. This section takes a deep dive down to the hardware, and then back up through the .NET CLR.
Garbage Collection and Resource Management
Programs need to store information as they execute. Managing memory is a decades-old problem. The CLR offers garbage collection, which takes away many potential opportunities for mistakes - but is certainly not magical. In this section its behavior and expectations are considered, along with the need to manage things that have limited lifetime, but are not good to leave to the GC.
Selecting Data Structures
There are a huge number of built-in data types in the .Net framework. Many are useful, but rarely used. Furthermore, there are a number of data structures not found in the class library that are incredibly useful, can be built with relative ease or found on NuGet, and are widely recognized and formalized. Choosing appropriate data structures is a huge part of effective program design.
The idea of objects has been around for decades, yet understanding of their power is still very unevenly distributed. Programming languages giving the illusion that classes are really just containers for procedures or useful for building record types has not helped matters. In this section well look at how objects can be used more effectively, and how understanding messaging is at the heart of good OO design.
Mutability, Immutability, and what lies between
For years, imperative programming - centered around manipulating state - has been contrasted with functional programming - centered around computing values. The former is sometimes said to perform better and be easier to learn, while the latter is said to produce code that is easier to reason about, compose, and parallelize. Both have something to offer.
Extensibility and Composability
Over time, needs change. We have to grow our software, and wed like to try and minimize risk as we do so. Since changing working code is risky, we tend to prefer designs that allow extension without modification. We sometimes also need designs that let us compose building blocks together in a safe and simple way. This section considers some strategies for achieving these goals.
Coping with, and tolerating, failures
Our software often must work with inherently unreliable components. Networks can fail, we can be handed corrupt data, databases can pick us as transaction deadlock victims, and so forth. How can we cope?
Working with time
Time is a gnarly domain. Humans talk plenty about dates and times, but often without precision and relying on context to resolve ambiguities. To make things worse, the .Net date/time API makes it all to easy for these ambiguities to make their way into software - often leading to difficult bugs. How can we do better?
Measuring and logging
Feedback loops are essential to a designer. These can occur at many levels. Key to all of them is ensuring the data obtained is reliable and complete enough.
After looking at a bunch of ideas for producing good designs, well spend a little time considering some problems to be on the watch for.