Home Programming Real-World Implementation of C# Design Patterns

Real-World Implementation of C# Design Patterns

By Bruce M. Van Horn II
books-svg-icon Book
eBook $37.99 $25.99
Print $46.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $37.99 $25.99
Print $46.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Chapter 1: There’s a Big Ball of Mud on Your Plate of Spaghetti
About this book
As a software developer, you need to learn new languages and simultaneously get familiarized with the programming paradigms and methods of leveraging patterns, as both a communications tool and an advantage when designing well-written, easy-to-maintain code. Design patterns, being a collection of best practices, provide the necessary wisdom to help you overcome common sets of challenges in object-oriented design and programming. This practical guide to design patterns helps C# developers put their programming knowledge to work. The book takes a hands-on approach to introducing patterns and anti-patterns, elaborating on 14 patterns along with their real-world implementations. Throughout the book, you'll understand the implementation of each pattern, as well as find out how to successfully implement those patterns in C# code within the context of a real-world project. By the end of this design patterns book, you’ll be able to recognize situations that tempt you to reinvent the wheel, and quickly avoid the time and cost associated with solving common and well-understood problems with battle-tested design patterns.
Publication date:
October 2022
Publisher
Packt
Pages
442
ISBN
9781803242736

 

There’s a Big Ball of Mud on Your Plate of Spaghetti

Welcome to what is potentially your last day on the job. Your project is about to be canceled. Your customers are angry. Your boss is freaking out. Your boss’s boss is in Antigua, but when she comes back next week, heads will probably roll. There’s no way to sugar-coat it. You might want to update your résumé and brush up on your algorithms, so that you’re ready for an imminent job search.

How did it come to this? We had a plan. The hardware architecture was simple. The first few releases went off without a hitch and our users were delighted. Our client even presented a whole new set of feature requests and signed a contract extension. How have we found ourselves at the precipice of sure and sudden doom?

The situation we have found ourselves in here is far from unique. According to many academic accounts, five out of six software projects are canceled. Others fail by running behind schedule or over budget. Software projects are hard. There’s no such thing as a simple program. There’s really no project that you can knock out in a week, then ship, and that’s the end. It doesn’t work that way. This phenomenon is unique to the software industry. Structural engineers who design bridge are pretty much done when the bridge opens for public traffic. Electrical engineers design and test circuits on breadboards, then hand those designs off to be manufactured. Aeronautical engineers, such as my grandfather, who designed power plants (as in, engines) for Beechcraft, generally designed and prototyped engines, but didn’t do much beyond that. It was up to others to manufacture the engine, mount it on the aircraft, and others to maintain the engine.

In contrast, software engineers must design, build, test, and often maintain the systems that they develop in a continuous delivery environment. Many projects are never “done.” I’ve been working on the same software project for the last 9 years. We certainly haven’t built the perfect project with perfect architecture, but the project has endured. New features are produced, and bugs are discovered and fixed.

What differentiates projects that run continually for many years from the vast majority that get canceled? While there are many ways this can happen, we’re going to focus solely on the design and architecture of our software. I’ll start with how it very often goes wrong. In keeping with the title of this book, we’ll spend some time in this chapter discussing a set of antipatterns. While I haven’t directly introduced the concept of patterns yet, I suspect that you can make an educated guess about what they are, along with what their antithesis is. A pattern, for now, is simply a formally explained, abstract, best practice solution to a common development requirement. An antipattern is a formal example of what you shouldn’t do. Patterns are arguably good. Antipatterns are inarguably bad.

This chapter will present some of the most common antipatterns, including the following:

  • Stovepipe systems
  • The Big Ball of Mud
  • The Golden Hammer

Once we’ve learned about a few antipatterns, we’ll focus in later chapters on principles and patterns designed to combat and correct the circumstances where antipatterns have either taken hold or might soon take hold. In Chapter 2, Prepping for Practical Real-World Applications of Patterns in C#, I will prepare you for your work with patterns. Software development is an odd business in that we all come to it traveling different roads. I am personally self-taught. I started when I was 12 years old. The only books about computer programming were available for purchase at Radio Shack. There were about a dozen. We didn’t have resources such as Packt Publishing plying the market with fascinating and useful books on every facet of software development. In 1991, the year that I graduated from university, a computer science degree would have focused on software development for mainframes using FORTRAN, which is a far cry from the work I do now. The mainframe programming course I took in 1987 was the last class to use punch cards. If you’re not sure what they are, go look them up. I’ll wait. Are you back? Are you horrified? Me too. The point is, there are a lot of people such as me out there who learned programming out of necessity, and they learned informally.  

There are many university-trained software developers out there, but not all software development programs are the same. Computer science programs focus on elements such as mathematical theory and algorithm development but teach only a minimal amount of practice. Software engineering programs, boot camps, and trade schools have more of an engineering focus, where you learn to build software with less of a focus on the theory. Regardless of where you started, Chapter 2 aims to ensure that you understand the most important formal engineering concepts needed in order to work with patterns. Patterns were created using a set of rules and Chapter 2 covers those rules.

In Chapters 3, 4, and 5, we cover patterns in earnest using a story format. I’ve done this in the hope of creating a learning experience very different than my own, having read some of the more heavy-handed academic treatments of design patterns. The patterns that I have selected for this book come from what is perhaps the seminal work on patterns in the software industry, Design Patterns: Elements of Reusable Object-Oriented Software, by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides. These four authors are known collectively as The Gang of Four (GoF), and the book that they wrote is colloquially referred to as The GoF book or just GoF. The GoF book contains 23 patterns, broken down into three categories:

  • Creational patterns deal with the creation of objects beyond simply using the new keyword.
  • Structural patterns deal with the way that you structure your classes to maximize flexibility, reduce tight coupling, and help you focus on reusability.
  • Behavioral patterns deal with how objects interact with one another.

The creational patterns I’ll be covering in Chapter 3 include the following:

  • The Simple Factory (technically not a pattern)
  • The Factory Method pattern
  • The Abstract Factory pattern
  • The Builder pattern
  • The Object Pool pattern
  • The Singleton pattern

Within the realm of structural patterns, in Chapter 4, I’ll be covering the following:

  • The Decorator pattern
  • The Façade pattern
  • The Composite pattern
  • The Bridge pattern

Practical pattern coverage will conclude in Chapter 5 with this set of behavioral patterns:

  • The Command pattern
  • The Iterator pattern
  • The Observer pattern
  • The Strategy pattern

Again, I’ll point out that this book is designed to focus on real-world software development. In the real world, we don’t always perfectly follow the rules presented in Chapter 2. Many books present a perfect experience. Expert authors always present all of their examples as perfect on the first try. I won’t be doing that because that isn’t in keeping with reality. As we flow through Chapters 3, 4, and 5, we will find ourselves faced with “gotchas” that you will find in actual practice. There’s no way around them. Even if you execute the patterns and strategies in these chapters perfectly, nobody can foretell the future. In Chapter 6, a plot twist in our story arises and we have a chance to rethink everything that we’ve done up to that point. In Chapter 6, we’ll design a new system based on an old one using the patterns that we’ve learned so far. Chapter 6 is all about creating a design and a plan. In Chapter 7, we implement that plan.  

I won’t be covering every pattern covered by the GoF. Instead, I’ll be focusing on the patterns you are most likely to need as a C# .NET software developer working in the field. I’ve selected my list of patterns based on popularity, usefulness, and complexity. Complex patterns that are not often seen in the wild have been omitted from the main text of the book. That said, I do circle back in Chapter 8 to give you a rundown on the patterns that I didn’t cover, and the usual advice on where to go from there.

This book assumes that you have a few years of experience working with C#. In addition to my day job, I have taught software development at colleges for the last 25 years. I presently teach at Southern Methodist University’s Full Stack Code Bootcamp. Some of the programs I taught focused on C#, while others haven’t. At SMU we teach JavaScript. If you’re coming to this book without recent C# experience, or perhaps with none at all, I’ve added Appendix 1 at the end of the book. It is designed to give you what I hope is enough orientation in the C# language to make the rest of the book useful. The truth about patterns is they are language-agnostic. They apply to any object-oriented language, and I’ve even seen some of them shoehorned into languages that are not object-oriented with arguable levels of success.

 

Technical requirements

This chapter presents some code examples. Most books always present exemplary code that makes sense for you to follow in the creation of the project. The code in this chapter is terrible on purpose. It isn’t strictly necessary that you follow along by creating the project, but you’re welcome to do so if you’d like.

If this is the case, you’ll need the following:

  • A computer running the Windows OS. I’m using Windows 10. Since the projects are simple command-line projects, I’m pretty sure everything here would also work on a Mac or Linux, but I haven’t tested the projects on these operating systems.
  • A supported IDE, such as Visual Studio, JetBrains Rider, or Visual Studio Code with C# extensions. I’m using Rider 2021.3.3.
  • Some version of the .NET SDK. Again, the projects are simple enough that our code shouldn’t be reliant on any particular version. I happen to use the .NET Core 6 SDK and my code’s syntax may reflect that.
  • An instance of SQL Server and basic knowledge of SQL. I want to restate that the code in this chapter is designed to be a realistic example of throwaway code. C# and SQL Server go together as peanut butter and jam do, which adds to the realism. Some readers may not be comfortable working in SQL Server, especially without Entity Framework (EF) used for its presentation. This is the only place in this book where a database is even mentioned. If you have no experience with databases, don’t worry. The example is really meant to be read more than tried. If you want to try it, any version of SQL Server should work. I’ll be using SQL Server 2019.

You can find the code files for this chapter on GitHub at https://github.com/Kpackt/Real-World-Implementation-of-C-Design-Patterns/tree/main/chapter-1/.

 

No battle plan survives first contact with the enemy

There’s an old saying: if you fail to plan, you plan to fail. Only the rankest amateur would dive into a project IDE-first without at least considering how the project ought to be structured. The typical first steps might involve roughing out a package and object structure, or maybe designing the structure of a relational database that will persist the data used by our software. Someone who’s got a few projects under their belt might even draw some diagrams using the Unified Modeling Language (UML).

We begin by taking a set of user stories and we shape our code into something that on the surface meets the requirements in front of us. Soon, we’re in an agile groove swing. We’ve achieved velocity! We create a feature, show it to the customer, get feedback, revise, and continuously deliver. That’s usually how the troubles begin.

Our first major anti-pattern, the stovepipe system, comes from the seminal book on the subject, AntiPatterns, by Brown, et al., which I’ve listed as suggested reading at the end of this chapter.

The Stovepipe system

Once upon a time, in just about any industrialized society, people heated their homes and cooked using a cast-iron potbelly stove. These stoves burned coal or wood for fuel. Over time, the exhaust vent for the stove, called the stovepipe, since it was literally a pipe sticking out of the stove, would build up with corrosive deposits, which led to leaky stovepipes. The fumes from a burning stove are potentially life-threatening within a small, enclosed space.

Here’s what an actual stovepipe looks like:

Figure 1.1 – A stove with a stovepipe.


Figure 1.1 – A stove with a stovepipe.

The stovepipe required constant maintenance to prevent asphyxiation. This was usually done by the owner of the stove, who was unlikely to be a stove repair professional. The stove was repaired using tools and materials that were readily available. This made for very ad hoc patch jobs, rather than clean, well-thought-out repairs done with original equipment manufacturer (OEM) grade materials and the proper tools.

Now, think about how this might relate to a software project. The initial release is designed with great care, with an implementation that perfectly matches the design. The natural tendency during software maintenance is to fix things quickly and get the patched version released and out the door. As with our amateurish stove repairs, our analysis of the holes in the software design and implementation are perfunctory and incomplete. There is pressure to solve this quickly. Everybody is watching you. Every minute the application is down costs the company money and you risk losing the employee of the month parking spot.

This happens to everyone and everyone generally caves to human frailties. You then implement the quickest, easiest thing you can think of – it’s done and the patch is out the door. The crisis is over.

Or is it? Dun dun dun! Small ad hoc fixes have a negative cumulative effect over time, referred to as technical debt, just as the corrosive deposits on a stovepipe do. How can you tell whether the systems that you’re working on are stovepipe systems? Let’s explore the following:

  • Stovepipe systems are monolithic by their very nature. It is not easy to get data in or out of this kind of system, and integrating software built this way into a larger enterprise architecture is cumbersome or impossible.
  • Stovepipe systems are very brittle. When you make one of these small ad hoc repairs, you generally find that the fix breaks other parts of the application.  Usually, this isn’t discovered until after the breaking fix has been released.
  • Stovepipe systems can’t be easily extended as new business requirements emerge. When a project starts, you’re given a set of requirements. You build the software that meets those requirements. After it’s released, a new feature is requested that you couldn’t possibly have predicted. You realize that there’s no way to implement that feature without redesigning the whole app. Anytime you’re tempted to throw out the baby with the bathwater and just start over, you’re working on a stovepipe system.
  • Stovepipe systems built on component architectures are generally incapable of sharing those components with other enterprise applications. The level of code reuse between projects is very low.
  • Stovepipe systems are often found on projects with high turnover. This makes sense. You start a new job, replacing the last developer, and you feel pressure to get something working quickly to show your new boss that hiring you wasn’t a huge mistake. You do your best to piece something together to fix a problem. You have no knowledge of the existing architecture or what’s been tried, and perhaps failed, in the past. Now amplify this by considering two or three further re-staffing efforts, with several months between each new hire’s start dates.
  • Stovepipe systems are often indicated when the development team is using new or unfamiliar technologies, stacks, or languages. Given that the same pressure to produce something quickly exists, while the team is simultaneously required to work with tools and languages that they’ve never used before, this leads to the same pattern of just getting something working and released. You will also encounter stovepipe systems in start-ups, corporate acquisitions, and mergers for these same reasons.

Does any of this sound familiar? Naturally, we’re not talking about anything you’ve ever written! Isn’t this just a little bit reminiscent of code you’ve seen other people write? Maybe your competitors? Maybe your students? Even if you own up to writing a stovepipe system, don’t beat yourself up. It is far and away the most popular pattern in software development today. Sometimes, a stovepipe system is fine. Remember, not every physical edifice needs to be supported by fluted ivory columns, and there’s a very legitimate argument to be made for getting the software to market and worrying about the rest later. However, if your objective is to build software that’s still useful and profitable 10 years or more down the road, keep reading. We’ll have those stovepipes replaced with functional, modular, well-constructed systems in no time.

 

The Big Ball of Mud

Around the same time Brown, et al. were writing their book on antipatterns, another research team was engaged in a similar effort. The product of their work was titled Big Ball of Mud, by Foote and Yoder (1997), which I’ve listed in the Further reading section at the end of this chapter. It’s their work that inspires the title of this chapter.

The Big Ball of Mud antipattern is remarkably similar to our outline of stovepipe systems. However, the authors go into greater depth on how these systems understandably come to be.

They often start with throwaway code. I think this is self-explanatory. It’s code that you knock out in a few hours, or even a few weeks, that serves as a rough prototype. It proves to you and perhaps your stakeholders that the problem in front of you is soluble. It might even be good enough to demonstrate to the client. This is where the trap is sprung. The prototype is good enough to publish, so, at the behest of your boss, you do. We’ll simulate this later in this chapter in a section titled A throwaway code example by intentionally building a prototype that is good enough to ship, but not good enough to survive extension. This rough prototype will do everything asked of us. Right after this imaginary software project ships its first release, we’ll then find ourselves faced with a second factor in the construction of a Big Ball of Mud: piecemeal growth. A project manager might refer to this pejoratively as scope creep. I have been a consulting software engineer and a corporate software engineer. I can tell you the project management view of scope creep as being something negative is misinformed. While it is a source of frustration from a planning and billing perspective, new requirements coming in after the initial release are the hallmark of a successful system. It is my strongest advice that you begin every project with the idea that it will be wildly successful. This may seem overly optimistic, but it is in fact the worst-case scenario if you have published throwaway code.

Piecemeal growth leads to a strategy called keeping it working. Again, this needs little explanation. As bugs and new features are identified, you just fix the offending bits and make the program satisfy the new set of requirements. And, oh, by the way, we need this done by next week.

After the second release, keeping it working becomes your daily job description. If your program is really successful, you will start to hire people to help you with keeping it working, which naturally amplifies the problems and technical debt as the project continues to grow.

To reiterate, this sounds very similar to our elucidation of a stovepipe system. Foote and Yoder consider in more detail the forces that lead to our unfortunate muddy circumstances. Forces, as in nature, are outside actors that you can rarely ever control. These forces consist of the following:

  • Time
  • Cost
  • Experience
  • Skill
  • Visibility
  • Complexity
  • Change
  • Scale

Let’s talk a little more about each.

Time

You may not be given enough time to seriously consider the long-term ramifications of the architectural choices that you’re currently making. Time also limits your project by limiting what you can accomplish with what you’re allotted. Most developers and project managers try to get around this by padding their estimates. In my experience, Parkinson’s law is true: projects where time estimates are padded, or even doubled, usually expand to fill or exceed the time allotted.

Cost

Most projects don’t have an infinite budget. Those that do are open source projects that have no monetary budget at all, instead substituting it with the time of volunteers, which is itself a cost. Architecture is expensive. The people with the knowledge and experience to develop a sound architecture are rare, though slightly less inaccessible given that you are reading this book. They tend to draw higher salaries, and the expense involved in the effort to create and maintain proper architecture doesn’t pay off immediately in the minds of your stakeholders, bosses, or customers.

Good architecture requires time, both on the part of the development staff and the architect, but also domain experts who know the business behind the software. The domain experts are rarely dedicated to the software development effort. They have regular jobs with real requirements and deadlines outside the software project. Involving a business consultant who bills at $250 USD per hour is eating up time that could be billable, but you honestly can’t complete the project without this access.

Experience

Software developers are experts in software development. They rarely have expertise in the business domain where they are building solutions. For example, it’s rare that someone building a system that quotes insurance policies has worked as an actuary or even an adjuster. Lack of experience in the business domain makes the job of modeling software a process of trial and error, which naturally affects the program’s architecture.

Skill

Not all software developers have equal levels of skill. Some are new to the field. Some are slower learners than others. Some learned to use a few golden hammers (more on this later) and refuse to upskill any further. And there’s always a superstar on the project that makes everybody else feel as though they’re a poser.

Visibility

You can’t see inside a working program. Sure, you can fire up a debugger, but normal people can’t look around the architecture of your code the same way that they can inspect the architecture of a physical structure such as an office building. For this reason, architecture is neglected. Your boss will not likely give you a fat bonus for your amazing abstractions and interface structures. They will, however, reward you for shipping early. This leads to a very human, lackadaisical attitude toward how your code is structured.

Complexity

Complex problem domains beget muddy architectures. Imagine modeling a collection of modern light bulbs. That’s pretty easy. Properties such as wattage, light output in lumens, and input voltage jump out at you as if they are second nature. Now, imagine modeling a light bulb in 1878. You’re in uncharted territory. Thomas Edison patented his first light bulb in 1879 and is famously quoted as saying that he had discovered two thousand ways to not build a light bulb. If the domain is complex or unexplored, you should expect a bumpy ride as far as your architecture is concerned.

Change

Change is the one thing that always remains constant. Foote and Yoder wrote that when we devise an architecture, it is based entirely upon a supposition: a set of assumptions about the future wherein we expect changes and extensions to that architecture to be bound only to the realm of possibilities that we have considered so far. This is all well and good, except another truism invariably surfaces: no battle plan survives first contact with the enemy. The change requests will come in the most inconvenient form, at the most inconvenient time, and it’s your job to deal with that. The easy way out is always the most palatable to the stakeholders but it is what leads to a Big Ball of Mud.

Scale

Creating a system to be used by 100 people in total is a very different problem than creating a system that can process 10,000 requests per second. The style in which you write your code is different. Your reliance on highly performant algorithms is largely absent in a small system, but vital to the success of a large one. Rarely do projects start at the scale typically considered by Google or Amazon. Successful projects must be able to scale up according to how successful they become.

 

The Golden Hammer

Another important antipattern you should learn to recognize is generally a product of some marketing organization or salesperson in a company outside your own. It happens when some killer app, framework, infrastructure component, or tool is presented as the panacea for all your software development woes. It slices, it dices, it makes julienne fries, and it automatically refactors itself while speeding up the execution of your code.

The antipattern is described as the Golden Hammer. Behold a fully-rendered CGI representation in Figure 1.2:

Figure 1.2 – When you’re given a golden hammer, everything is a nail.

Figure 1.2 – When you’re given a golden hammer, everything is a nail.

Silicon snake oil salespeople will visit you, take you out to someplace fancy, and try to convince you that the database tool, platform, or whatever-as-a-service (WaaS) that they’re selling can be the entire basis for your company’s software. Consider Microsoft SQL Server for a minute. At its most basic, SQL Server is a relational database. It stores your data in tables that you can query. Related tables of data can be joined and filtered allowing a developer who understands the Structured Query Language (SQL) to produce reporting data in any format or configuration. This is a common functionality found in every relational database tool from Microsoft Access and SQLite to Oracle and Microsoft SQL Server. Since SQL is a standardized language, offering this basic functionality is little more than table stakes. Just so we understand each other, all puns are intended.

So, how could Microsoft expect to charge money for something that you can get for free in open source offerings such as MySQL and PostgreSQL? Granted, SQL Server got started before they did when there were fewer rivals in the marketplace, but SQL Server is one of the most popular platforms for managing data today. This is because SQL Server’s value contribution doesn’t end with tabular data storage. As the product has grown over the years, new features and ancillary tools have been added. You have the ability to load and analyze data in novel and sophisticated ways using SQL Server Analysis Services. SQL Server Reporting Services allows you to create reports using SQL and then present those reports graphically to whoever might need them by emailing the reports as PDFs. It also allows users to access the report on the server and play around with the data without needing to know SQL or have access to the underlying code.

There are supported workflows for working with AI and machine learning projects using R and Python, and you can make bits of code in C# that process in the database such as a native stored procedure. SQL Server Integration Services allows you to ingest and publish data to a variety of different databases, software services, and industry formats. This leads to the ability to integrate your software and services with your business partners and customers.

In short, if you tried hard enough, you could probably write a great deal of an application, if not an entire one, solely using SQL Server’s ecosystem. SQL Server is the Golden Hammer. Every problem now looks as if it’s something that can be solved with SQL Server. I want to point out that I am not vilifying SQL Server. It’s a reliable and cost-effective set of tools. I go out of my way to recommend it at parties and my advice is always well received. Note to self: find better parties. I picked on SQL Server because I’ve seen it happen with this particular tool. If you spend too much time reading the marketing material for SQL Server, it would be easy for you to walk away with the same conclusion: that SQL Server is all you need. Maybe it is, but you should only make that decision after you understand the Golden Hammer antipattern, lest you wind up painting yourself into a technological corner.

The Golden Hammer also emerges when a developer learns about some technology that was unknown to them before. They use it. They like it. They’re rewarded for it in the form of fast or novel solutions to a problem. Since that worked out so well, and since they’ve gone to the effort of adding a new skill to their skill set, they try to use that tool or technique to solve every problem that they encounter.

Once, I took over a project that was in trouble. The lead programmer on a small team was being let go and most of his team left shortly after I replaced him. Interpersonal drama aside, I set out to understand the new project and the business domain by going through the existing code base.

I asked around and, as it turned out, the original staff on the project were with a consulting firm. The firm sent a couple of their ace developers over to meet and gather requirements. Upon seeing the prototypes that the client had themselves produced in Excel, using Visual Basic for Applications (VBA), the consultants concluded that they could produce real code in a real language and have a fully converted program running in under a month.

Two years went by with no usable deliverables. The ace developers either grossly underestimated the prototype they were working with or overestimated their capabilities. I think it was a little of both. Most developers look down their noses at VBA. I’ll admit that I used to, even though I’ve written quite a bit of VBA code. The consultants erroneously concluded that VBA is simplistic. They believed anything written in VBA would involve a trivial amount of effort to convert to a language as powerful as C#, backed by the equally powerful .NET Framework and SQL Server.

After a few months with very little progress, the consulting firm pulled their ace developers off the project to work on something else and the project was staffed entirely by junior developers.

Given the antipatterns we’ve covered so far, you can already see where this story is headed. I inherited this code after two and half years without a viable release. As I went through the existing code, I was able to see where the junior developers had encountered some tool or technique. It was as if I was looking at rings on a tree:

Figure 1.3 – The effects of new developers discovering a golden hammer are analogous to tree rings in their code.

Figure 1.3 – The effects of new developers discovering a golden hammer are analogous to tree rings in their code.

You can tell exactly where they have learned that a stored procedure can be used in SQL Server because, from that point forward, the business logic suddenly moved out of the code and into the database. This is usually a bad idea. It’s often done because you can change the business rules without compiling and publishing a new executable, allowing you to make minor or major adjustments. This is roughly akin to working on the engine of an airplane while it’s flying at 1,261 knots (about 1,453 mph or 2,336 km/h) at 30,000 feet (9,144 m).

Somewhere else, you can tell they have read a book on patterns because the code changes. Suddenly, everything has interfaces and uses the factory pattern, which we’ll cover later in Chapter 3. Some of this was good. I could see that they were improving. However, a lot of it was someone picking up a new hammer and using it to bang everything around it into the shape of something useful. This was evident mainly because they were never given a chance to go back and refactor their earlier work. They used different techniques at different points in time. They weren’t always using the best tool for the job, but they were motivated by the forces we discussed earlier. They did their best with what they had, as with our stovepipe repair jobs.

 

A throwaway code example

Let’s take a look at some throwaway code. Remember, throwaway code is written quickly, with little thought to architecture. It’s good enough to ship but not good enough to survive extension. Consider a program designed to ingest log data from a popular web server, and subsequently analyze and present salient information in the form of a report rendered in HTML. You will be analyzing logs from NGINX (pronounced ‘engine-ex’), one of the most popular web server programs in use today. I usually write a user story in an issue tracker, but I’ll write it as a Markdown file in lieu, and I’ll include it with my project so that I have a record of my requirements:

As an IT administrator, I would like to be able to easily review weblog traffic by running a command that takes in the location on my computer of a log file from a server running NGINX. I would also like to store the data in a relational database table for future analysis.
GIVEN: I have a log file from NGINX on my computer at c:\temp\nginx-sample.log AND
GIVEN: I have opened a PowerShell terminal window in Windows 10 or later AND
GIVEN: The WebLogReporter program is listed within my computer's PATH environment variable.
THEN: I can run the WebLogReporter command, pass the location of the weblog and the path for the output HTML file.
GIVEN: The program runs without errors.
THEN: I am able to view the output HTML file in my favorite browser.
Acceptance Criteria:
* It's done when I can run the WebLogReporter program with no arguments and receive instructions.
* It's done when I can run the WebLogReporter program with two arguments, consisting of the first being a full path to the NGINX log file I wish to analyze and the second being the full path to the output HTML file I would like the program to produce, and I am able to view the output HTML file within my browser.
* It's done when all the log data are stored in a relational database table so I can query and analyze the data later.

Your team decides to use C# and SQL Server to read, parse, and store the data for analysis. They decide that, while there are several good templating systems out there, nobody on the team has ever used any of them. Time is short and HTML is simple, so we’ll just write our own code to convert our results represented by the results of SQL statements. Let’s dive in! The requirements stipulate a console application, so that’s the project type I used when creating it in my IDE. I won’t be walking you through creating the project. I’m assuming you know how to create a console application using the new project options in Visual Studio.

The input data from an NGINX log looks as follows:

127.0.0.1 - - [16/Jan/2022:04:09:51 +0000] "GET /api/get_pricing_info/B641F364-DB29-4241-A45B-7AF6146BC HTTP/1.1" 200 5442 "-" "python-requests/2.25.0"
127.0.0.1 - - [16/Jan/2022:04:09:52 +0000] "GET /api/get_inventory/B641F364-DB29-4241-A45B-7AF6146BC HTTP/1.1" 200 3007 "-" "python-requests/2.25.0"
127.0.0.1 - - [16/Jan/2022:04:09:52 +0000] "GET /api/get_product_details/B641F364-DB29-4241-A45B-7AF6146BC HTTP/1.1" 200 3572 "-" "python-requests/2.25.0"

When you create a console app project in Visual Studio, it creates a file called Program.cs. We’re not going to do anything with Program.cs yet. I’m going to start by creating a new class file to represent a log entry. I’ll call it NginxLogEntry. I can see in my sample data that we have a date field, so I know that I’m going to need internationalization, owing to the cultural info needed to render a date. So, let’s get the basics in place with a using statement for the globalization package, a namespace, and the class. Visual Studio likes to mark the classes with an internal access modifier. Call me old-fashioned. I always change them to public, assuming that’s appropriate, and in this case, it is:

using System.Globalization;
namespace WebLogReporter
{
  public class NginxLogEntry
  {
     //TODO:  the rest of the code will go here
  }
}

With the basics out of the way, let’s set up our member variables. Aside from a couple of constructors, that’s really all we’ll need since this class is designed to represent the line entries in the log.  

The fields we’re interested in are visually identifiable in the preceding data sample:

  • ServerIPAddress represents the IP address of the web server from which the log was taken.
  • RequestDateTime represents the date and time of each request in the log.
  • Verb represents the HTTP verb or request method. We’ll be supporting four, though there are many more available.
  • Route represents the route of the request. Our sample is from a RESTful API.
  • ResponseCode represents the HTTP response code for the request. Successful codes are in the 200 and 300 range. Unsuccessful codes are in the 400 and 500 range.
  • SizeInBytes represents the size of the data returned by the request.
  • RequestingAgent represents the HTTP agent used to make the request. This is usually a reference to the web browser used, but in all the cases in our sample, it is a client written in Python 3 using the popular requests library.

In addition to our fields, I’ll start with an enum to store the four acceptable values for the HTTP methods, which I’ve called HTTPVerbs. The rest are represented with straightforward auto-properties:

    public enum HTTPVerbs { GET, POST, PUT, DELETE }
    public string ServerIPAddress { get; set; }
    public DateTime RequestDateTime { get; set; }
    public HTTPVerbs Verb { get; set; }
    public string Route { get; set; }
    public int ResponseCode { get; set; }
    public int SizeInBytes { get; set; }
    public string RequestingAgent { get; set; }

Now that I’ve got my enumeration and properties in place, I’m going to make a couple of constructors. I want one constructor that allows me to pass in a line from the log. The constructor will parse the line and return a fully populated class with the log line as an input. Here’s the top of the first constructor:

    public NginxLogEntry(String LogLine)
    {

First, I’ll take the log line being passed in and split it into a string array, using the .Split() method, which is part of the string class:

      var parts = LogLine.Split(' ');

While developing, I run into some corner cases. Sometimes, the log lines don’t have 12 fields, as I expect. To account for this, I add a conditional that detects log lines that come in with fewer than 12 parts. This rarely happens but when it does, I want to send them to the console so that I can see what is going on. This is the kind of thing you’d probably take out. I’m embracing my inner stovepipe developer here, so I’m leaving it in:

      if(parts.Length < 12)
      {
        Console.WriteLine(LogLine);
      }

Now, let’s set to work taking apart the line based on the split. It’s pretty easy to pick out the server IP address as the first element of the split array:

      ServerIPAddress = parts[0];

We don’t care about those two dashes in positions 1 and 2. We can see the date in the third position. Dealing with dates has always been slightly more fun than your average root canal. Think about all the formatting and the parsing needed just to get it into something we know will work with the database code that we’ll eventually write. Thankfully, C# handles this with aplomb. We pull out the date parts and we use a custom date format parser. I don’t really care about expressing the date in terms of locale, so I’ll use InvariantCulture as the second argument in the date parse:

      var rawDateTime = parts[3].Split(' ')[0].Substring(1).Trim();
      RequestDateTime = DateTime.ParseExact(rawDateTime, "dd/MMM/yyyy:HH:mm:ss", CultureInfo.InvariantCulture);

Next, we get to work parsing the HTTP verb. It needs to conform to the enum we defined at the top of the class. I start by pulling the relevant word and making sure it’s clean by giving it a quick trim. Then, I cast it to the enumeration type. I probably should have used tryParse(), but I didn’t. It still works with the input sample if I don’t, and that’s the kind of thinking that lands us in a stovepipe prison later:

      var rawVerb = parts[5].Trim().Substring(1); 
      Verb = (HTTPVerbs)Enum.Parse(typeof(HTTPVerbs), rawVerb); 

The Route value, the ResponseCode value, and the SizeInBytes value are just grabbed based on their position. In the latter two cases, I just used int.parse() to turn them into integers:

      Route = parts[6].Trim();
      ResponseCode = int.Parse(parts[8].Trim());
      SizeInBytes = int.Parse(parts[9].Trim());

Lastly, I need the RequestingAgent. The sample data has some pesky double quotes that I don’t want to capture, so I’ll just use the string.replace() method to replace them with null, effectively getting rid of them:

      RequestingAgent = parts[11].Replace("\"", null);
    }

I now have a very useful constructor that does my line parsing for me automatically. Nice!

My second constructor is more standard fare. I’d like to create NginxLogEntry by simply passing in all the relevant data elements:

    public NginxLogEntry(string serverIPAddress, DateTime 
    requestDateTime, string verb, string route, int 
    responseCode, int sizeInBytes, string requestingAgent)
    {
      RequestDateTime = requestDateTime;
      Verb = (HTTPVerbs)Enum.Parse(typeof(HTTPVerbs), 
              verb);
      Route = route;
      ResponseCode = responseCode;
      SizeInBytes = sizeInBytes;
      RequestingAgent = requestingAgent;
    }
  }
}

This class begins as all do – with property definitions. We have a requirement to store the log data in SQL Server. For this, I created a database on my laptop running SQL Server 2019. If you don’t have any experience with SQL Server, don’t worry. This is the only place it’s mentioned. You don’t need SQL knowledge to work with patterns in this book. I created a new database called WebLogEntries, then created a table that matches my object structure. The Data Definition Language (DDL) to create the table looks as follows:

CREATE TABLE [dbo].[WebLogEntries](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [ServerIPAddress] [varchar](15) NULL,
    [RequestDateTime] [datetime] NULL,
    [Verb] [varchar](10) NULL,
    [Route] [varchar](255) NULL,
    [ResponseCode] [int] NULL,
    [SizeInBytes] [int] NULL,
    [RequestingAgent] [varchar](255) NULL,
    [DateEntered] [datetime] NOT NULL,
    CONSTRAINT [PK_WebLogEntries] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
      IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
      ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = 
      OFF) ON [PRIMARY]
) ON [PRIMARY]

As you can see, I have added the ubiquitous auto-incrementing primary key field, simply called id. I also added a field to track when the record was entered and set its default value to SQL Server’s GETDATE() function, which yields the current date on the server.

Let’s move on to the code that reads and writes data with SQL Server. I think most people would use an Object-Relational Mapper (ORM) such as .NET’s EF for this. I prefer to leverage the control and performance I get from working directly with the database. In my IDE, I’ll create a second class called SQLServerStorage. If you’re following along, don’t forget to add the Systems.Data package via NuGet.

As before, I’ll start with the dependencies:

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;

Next, I’ll set up the class:

namespace WebLogReporter
{
  public class SQLServerStorage
  {
      //TODO:  the rest of the code goes here
  }
}

Unlike the data class we created earlier, this one is all about the methods. The first method I’ll make stores the data in the database using a direct connection. If you’ve only ever used EF, and you understand SQL (which you should), I highly recommend you try this style and test it for speed against your usual EF-driven code. You’ll see a huge difference, especially at scale. I’ll get off my proverbial soapbox now and get back to creating the StoreLogLine method. It takes in the NginxLogEntry class that we just wrote as its sole input:

    public void StoreLogLine(NginxLogEntry entry)
    {

Next, let’s connect to the database. I use the using syntax for this. If you’ve not used this before (see what I did there?), it’s very convenient since it handles the timely closure and destruction of whatever you create. In this case, I’m creating a database connection. Even in throwaway code, there are things you just don’t do, such as open a connection to a resource and fail to close it. It’s just so rude! This line sets up my connection. I also recommend a strong database password. As usual, I can hide behind the excuse that it’s throwaway code. At this point, I’ve likely repeated this more times than your local government has told you to wear a mask. And as with your local government, it probably won’t be the last time you hear it:

      using (SqlConnection con = new 
             SqlConnection("Server=Localhost;Database=
                           WebLogReporter;User 
                           Id=SA;Password=P@ssw0rd;"))
      {

Next, I’ll build my Data Manipulation Language (DML) statement for inserting data into the database using the connection we just forged. I’ll use the StringBuilder class, which is part of System.Text:

       var sql = new StringBuilder("INSERT INTO 
                [dbo].[WebLogEntries] (ServerIPAddress, 
                RequestDateTime, Verb, Route, ResponseCode, 
                SizeInBytes, RequestingAgent) VALUES (");
        sql.Append("'" + entry.ServerIPAddress + "',");
        sql.Append("'" + entry.RequestDateTime + "', ");
        sql.Append("'" + entry.Verb + "', ");
        sql.Append("'" + entry.Route + "', ");
        sql.Append(entry.ResponseCode.ToString() + ", ");
        sql.Append(entry.SizeInBytes.ToString() + ", ");
        sql.Append("'" + entry.RequestingAgent + "')");

Next, let’s open the connection, then execute our SQL statement:

        con.Open();
        
        using(SqlCommand cmd = con.CreateCommand())
        {
          cmd.CommandText = sql.ToString();
          cmd.CommandType = System.Data.CommandType.Text;
          cmd.ExecuteNonQuery();
        }
      
      }
    }

Fabulous! Now that we’re writing data, it stands to reason that we should also read it. Otherwise, our class would be really cruddy. Or maybe it wouldn’t be? I’ll let you mull that over while I type out the next method signature:

    public List<NginxLogEntry> RetrieveLogLines()
    {

The read method is going to return a list of NginxLogEntry instances. This is why we made that second constructor in the NginxLogEntry class earlier. I’ll start by instantiating an empty list to use as the return value. After that I’ll make a really simple SQL statement to read all the records from the database:

      var logLines = new List<NginxLogEntry>();
      var sql = "SELECT * FROM WebLogEntries";

Using the same using syntax as before, I’ll open a connection and read the records:

      using (SqlConnection con = new 
            SqlConnection("Server=Localhost;Database=
            WebLogReporter;User Id=SA;Password=P@ssw0rd;"))
      {
        SqlCommand cmd = new SqlCommand(sql, con);
        con.Open();
        SqlDataReader reader = cmd.ExecuteReader();

With the select statement executed, I’ll use a reader to get the data out line by line, and for each record, I’ll instantiate a NginxLogEntry class. Since it’s supposed to be prototype code, I’m relying on the positions in the dataset for data retrieval. This is not uncommon, but it is fairly brittle. A restructuring of the table will break this code later. But it’s throwaway code! See? I told you that you’d hear it again:

        while (reader.Read())
        {
          var serverIPAddress = reader.GetString(1);
          var requestDateTime = reader.GetDateTime(2);
          var verb = reader.GetString(3);
          var route = reader.GetString(4);
          var responseCode = reader.GetInt32(5);
          var sizeInBytes = reader.GetInt32(6);
          var requestingAgent = reader.GetString(7);
          var line = new NginxLogEntry(serverIPAddress, 
                     requestDateTime, verb, route, 
                     responseCode, sizeInBytes, 
                     requestingAgent);

Now that I’ve constructed the object using the data from the table, I’ll add it to my logLines list and return the list. The using statement handles the closure of all the database resources that I created along the way:

          logLines.Add(line);
        }
      }
      return logLines;
    }
  }
}

To sum it up, the class has two methods. The first, StoreLogLine, takes an instance of the NginxLogEntry class and converts the data into a SQL statement compatible with our table structure. We then perform the insert operation. Since I used the using syntax to open the connection to the database, that connection is automatically closed when we leave the scope of the method.

The second operation works in reverse. RetrieveLogLines executes a select statement that retrieves all our data from the table and converts it into a list of NginxLogEntry objects. The list is returned at the close of the method.

The last component is the output component. The class is called Report. Its job is to convert the records requested from the database into an HTML table, which is then written to a file.

I’ll set up the class with the dependencies and begin the class with the usual setup:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WebLogReporter
{
  public class Report
  {
      //TODO: the rest of your code goes here
  }

Next, I’ll add the method to generate the report:

    public void GenerateReport(string OutputPath)
    {

I’ll now use the SQLServerStorage class that we made earlier:      

var database = new SQLServerStorage();
var logLines = database.RetrieveLogLines();

I have the data. Now, I’ll use another StringBuilder to generate the HTML. It’s table code because this is absolutely not a book on frontend design:

      var output = new 
                   StringBuilder("<html><head><title>Web 
                   Log Report</title></head><body>");
      output.Append("<table><tr><th>Request 
                     Date</th><th>Verb</th><th>Route</th>
                     <th>Code</th><th>Size</th><th>Agent
                     </th></tr>");
      foreach (var logLine in logLines)
      {
        output.Append("<tr>");
        output.Append("<td>" + 
            logLine.RequestDateTime.ToString() + "</td>");
        output.Append("<td>" + logLine.Verb + "</td>");
        output.Append("<td>" + logLine.Route + "</td>");
        output.Append("<td>" + 
            logLine.ResponseCode.ToString() + "</td>");
        output.Append("<td>" + 
            logLine.SizeInBytes.ToString() + "</td>");
        output.Append("<td>" + 
            logLine.RequestingAgent.ToString() + "</td>");
        output.Append("</tr>");
      }
      output.Append("</table></body></html>");

Finally, we have a wonderful C# one-liner to output the file so that it’s ready for viewing in your favorite browser:

     File.WriteAllText(OutputPath, output.ToString());
    }
  }
}

It might be ugly, but it works. I’ll say it once again just because I can. It’s throwaway code! One trick I advocate when writing throwaway code is to make it so incredibly ugly that nobody in their right mind would put their name to it. I think I’ve accomplished that here. I just used a string builder to create my HTML. No spaces or formatting. It’s basically a minified HTML file, which is, of course, an intended feature and not at all inspired by laziness.

There’s one last thing to do before we put this baby to bed. We need to edit the Program.cs file Visual Studio created as the project’s entry point. This file glues all the other pieces together. The most recent editions of most C# IDEs generate the entry point for console apps within the Program.cs file. This isn’t new. What is new is the format this file takes. The new format lacks the usual constructor and class setup we’ve seen so far in the classes we created from scratch. Behind the scenes, the compiler is generating those definitions for us, but it makes Program.cs look different from everything else. Rather than present all the usual boilerplate, it’s straight to business.

We’ll start by using the WebLogReporter class that we just created:

using WebLogReporter;

We’ll do a perfunctory and minimal test to make sure the right number of arguments were passed in from the command line. We need a path to the log file and an output path. If you don’t pass in the right number of arguments, we’ll give you a little command-line hint, then exit with a non-zero code, in case this is part of some sequence of automation. I know, it’s throwaway code, but I’m not a barbarian:

if (args.Length < 2)
{
  Console.WriteLine("You must supply a path to the log file 
    you want to parse as well as a path for the output.");
  Console.WriteLine(@"For example: WebLogReporter 
    c:\temp\nginx-sample.log c:\temp\report.html");
  Environment.Exit(1);
}

Now, we check whether the log input file exists. If it doesn’t, we alert the user to our disappointment and again exit with non-zero code:

if (!File.Exists(args[0]))
{
  Console.WriteLine("The path " + args[0] + " is not a 
    valid log file.");
  Environment.Exit(1);
}

If they make it this far, we’re assuming everything is good and we get to work:

var logFileName = args[0];
var outputFile = args[1];
Console.WriteLine("Processing log: " + logFileName);
int lineCounter = 0;

We instantiate SQLServerStorageClass so we can store our records as we read them in:

var database = new SQLServerStorage();

Now, we open the input log file, and with a foreach loop, we take each line and use our parsing constructor in NginxLogEntry to create an NginxLogEntry object. We then feed that to our database class. If we encounter a line that’s a problem, we write out a message that states where the problem occurred so that we can review it later:

foreach(string line in 
        System.IO.File.ReadLines(logFileName))
{
  lineCounter++;
  try
  {
    var logLine = new NginxLogEntry(line);
    database.StoreLogLine(logLine);
  }
  catch 
  { 
    Console.WriteLine("Problem on line " + lineCounter); 
  }
}

We’ve parsed the log data and written it to the database. All that’s left is to use the Report class to write out our HTML:

var report = new Report();
report.GenerateReport(outputFile);
Console.WriteLine("Processed " + lineCounter + " log 
                  lines.");

To sum up, the Program.cs file contains the main program. The current version of C# allows us to dispense with the usual class definition in the main file for the project.

First, we check to make sure the user entered two arguments. It’s hardly a bulletproof check, but it’s good enough to demo.

Next, after making sure the input log file is a legitimate path, we open the file, read it line by line, and save each line to the database.

Once we’ve read all our lines, we read back the data from the database and convert it to HTML using the report object.

Your program is complete; you demonstrate it to the customer, and they are delighted! A week later, their boss has lunch with your boss, and a new requirement comes in stating the client would now like to support logs from two other web server formats: Apache and IIS. They’d also like to select the output from several different formats, including the following:

  • HTML (which we have already delivered)
  • Adobe PDFs
  • Markdown
  • JavaScript Object Notation (JSON)

The purpose of the last format, JSON, is that it allows outside clients to ingest that data into other systems for further analysis, such as capturing trend data over time.

While these concise descriptions of the requirements are hardly what we’d want when building a real extension to our program, they are enough to get you thinking.

What would you do?

Have we built a stovepipe system? If not, is there a chance it might evolve into one? Pause for a moment and think about this before reading further.

I believe we have built a stovepipe system. Here’s why:

  • Our classes are all single-purpose and coupled directly to the web server log formats
  • Our software is directly coupled with SQL Server
  • Our output to HTML is the only possible output, given that we didn’t create an interface or structure to abstract the output operation and format

You might be thinking that the second set of requirements was unknown at the time we created our first release. That’s accurate. You might further defend your idea by stating you are not psychic, and there’s no way you could have known you would need to extend the program per the second set of requirements. You’re right there too. Nobody is prescient. But despite that, you know, if for no other reason than you’ve read this chapter so far, that any successful program must support extension. This is true because you know your first iteration has now begotten a request for a second and that always entails changes and additions to the requirements. We can never know how the requirements will change, but we do know that they will change.

 

How can patterns help?

These factors are undoubtedly part of your professional life right now. Perhaps only a few of them are at play at a time. If you stay in software development for any length of time, you’ll undoubtedly encounter them all at some point. Remember, everything we’ve talked about so far is an antipattern. All this negative energy needs a counterbalance. You might even be tempted to say that you need to bring balance to the force. Instead of becoming a dark Jedi, maybe something less radical will do. You can learn to use patterns to balance and ultimately defeat the antipatterns and the forces that create and enable them.

I think it’s time we formally introduced the concept that I have left to your imagination thus far. I could offer my own definition of patterns but I’d rather stand on the shoulders of giants. From the days since Grace Hopper logged the first bug on the Mark II at Harvard in 1947 (see Figure 1.4), programmers and computer scientists have been facing the same problems over and over again. We get a little bit smarter every time and we write down what we did. If you take the distilled experience and knowledge gleaned through hard-won trial and error over the course of seven decades, from the early wartime pioneers to the most recent graduates, you wind up with a set of patterns, which are descriptions of solutions to consistently recurring problems.

Figure 1.4 – The very first computer bug was literally a bug (moth) that had crawled into the relays of the Mark II computer at Harvard University

Figure 1.4 – The very first computer bug was literally a bug (moth) that had crawled into the relays of the Mark II computer at Harvard University

The idea of patterns originated in the field of architecture, that is, traditional architecture, with respect to the design and creation of buildings. In 1977, Christopher Alexander documented a pattern language designed to form the basis for the best practices for building towns. The book describes two hundred and fifty-three patterns presented as the paragon of architectural design. The book breaks everything down into objects. I find it fascinating that the language doesn’t even change when you adapt the seminal book on architectural patterns to our explanation of software patterns. I’d characterize the book as being the synthesis of a language used to describe objects in the real world and how to mold spaces and objects together to achieve harmony. As with the motto of the Vulcans in the TV and film franchise Star Trek, the goal of the language of patterns is infinite diversity expressed in infinite combinations. Alexander himself describes a pattern as a solution to problems that occur frequently enough that every practitioner will likely recognize them. He then describes a solution in a way that isn’t directly tied to any implementation. Keeping it flexible in this way, the same solution may be used on a million projects and in a million slightly different ways.

Let’s shift focus from the world of building architecture to the realm of software architecture. We revisit the famous GoF. They define a design pattern as an abstraction of a recurring problem that pinpoints the chief elements of design structure, focusing on the idea of creating reusable object-oriented software.

Patterns are going to be the weapon that we can use to overcome the antipatterns and dark forces that prevail in both the loftiest corporate institutions and most hallowed halls in small business.

Are you ready to fight the darkness? Roll up your sleeves and let’s get to work!

 

Summary

This chapter initiated our discussion of patterns by defining antipatterns.  

We learned that if we design software only with a mind toward meeting the requirements, we will build a system that can’t be extended easily. These systems are called Stovepipe systems because over time, they degenerate structurally as an exhaust vent on a coal-burning stove does. You will invariably reach a point where maintaining and extending such a system is untenable. Nobody wants to be the one to tell the bosses that you need six months with no new releases so that you can rebuild the company’s cash-cow product. Designing with patterns will help you avoid these kinds of pitfalls.

We also learned about a similar antipattern called the Big Ball of Mud. Foote and Yoder described the prevailing forces that we all recognize in our daily work lives: time, cost, experience, skill, visibility, complexity, change, and scale. These forces confound our ability to write good code in the first place. Even if we can write good code for our first release, these forces erode systems over time, such as a tiny stream forming the Grand Canyon.

We saw an example of throwaway code, which is how most projects are born. We now know what bad practice it is to just clean up the throwaway code and then ship it. It pays to spend the time to architect projects properly and assume the worst-case scenario: that your software will be wildly successful. If it is, you can absolutely count on feature requests and new requirements that you never even imagined when you wrote the throwaway prototype.

Patterns can be thought of as a language that defines common software design elements, coupled with an abstract solution that can be implemented in many different ways. They aren’t tied to a particular language or technology stack. As you learn to use patterns, your software will become more robust owing to a stronger foundation. Your projects will be able to support your inevitable success by providing avenues for future features and expansion that you couldn’t have conceived of when you started your project.

In the next chapter, I’ll prepare you for your patterns journey with the C# programming language. I will cover some popular idioms and practices in relation to object-oriented programming. In particular, pay close attention to the presentation of SOLID methodology, because it is the foundation upon which successful patterns are implemented.

If you are new, or perhaps returning to C# having worked with other object-oriented languages, I want to direct you to Appendix 1 at the end of the book. I wrote this for my students and colleagues who have an interest in learning about patterns but have focused on other languages, such as JavaScript or Python. Patterns are language-agnostic. Learning these patterns exclusive of a language is possible. However, I believe that we all learn best by doing, and that means you need an implementation language. The neat thing about programming languages is that they are pretty much all the same. All of them use variables, objects, methods, collections, and loops.  

In Appendix 1, I’ll cover the object-oriented features of the C# language. I’ve alluded to my idea that newer developers within the field are the most at risk in terms of knowing the language reasonably well but simultaneously clinging to Golden Hammers and using C# to produce stovepipe software. I have a great many students who learn JavaScript from me and who, at my strong encouragement, want to take the next steps in their journey by learning C#. Given how differently inheritance and common structures such as objects and classes work between the two languages, you’ll undoubtedly detect my desire to be inclusive. I had originally written this appendix to be Chapter 2, but I didn’t want to bear the expense of having the C# crowd cry out, “I’m BORED.” Even if you’re a C# veteran, I encourage you to peruse the chapter. You might find I’ve explained things a little differently from many other authors, and certainly differently from an academic textbook.

 

Questions

  1. What, in your own words, is a pattern? What is an antipattern?
  2. What antipatterns have you seen in your own work before?
  3. What is a stovepipe system? Can you point to an example from your own body of work? Don’t worry, I won’t tell.
  4. Can you think of a time when you wielded a Golden Hammer? What was the hammer and what did you perceive as the nails?
 

Further reading

  • Alexander, C. (1977). A pattern language: towns, buildings, construction. Oxford University Press
  • Brown, W. H., Malveau, R. C., McCormick, H.W. S., & Mowbray, T. J. (1998). AntiPatterns: refactoring software, architectures, and projects in crisis. John Wiley & Sons, Inc.
  • Foote, B., & Yoder, J. (1997). Big Ball of Mud. Pattern languages of program design, 4, 654–692. Retrieved from http://www.laputan.org/pub/foote/mud.pdf
  • Gamma, E., Helm, R., Johnson, R., Vlissides, J., & Patterns, D. (1995). Elements of reusable object-oriented software (Vol. 99.) Reading, Massachusetts: Addison-Wesley
  • Johnson, J. (1995). Creating Chaos. American Programmer, July 1995
  • Nazeer, H. (2020). A pattern language: towns, buildings, construction (review). Journal of Research in Architecture and Planning, Vol. 29, Second Issue
About the Author
  • Bruce M. Van Horn II

    Bruce M. Van Horn II is a professional software engineer with over 30 years' programming experience developing large-scale commercial applications used by millions of users every day. He works as the lead developer for Clear Technologies, in Dallas, Texas, as the Engineering lead for its Visual Storage Intelligence product. For 25 years, he has also taught evening classes at the collegiate and university level in various programming, animation, and multimedia disciplines. He currently teaches Full Stack Web Development at Southern Methodist University. When not writing code or wrangling his two young daughters, he enjoys designing video games and creating CGI-based art and animation.

    Browse publications by this author
Latest Reviews (1 reviews total)
One of the best books on design patterns.
Real-World Implementation of C# Design Patterns
Unlock this book and the full library FREE for 7 days
Start now