Author: erik

  • [Review] Naming Things: The Hardest Problem in Software Engineering

    [Review] Naming Things: The Hardest Problem in Software Engineering

    In today’s post, I review Naming Things: The Hardest Problem in Software Engineering, by Tom Benner.

    Read more: [Review] Naming Things: The Hardest Problem in Software Engineering
    NOTE: At erikscode.space, book reviews do not contain affiliate links or paid advertisements. They are not written at the request of one of the authors or publishers, paid or otherwise. There are no incentives that would sway this review in a positive or negative direction. This review is based purely on the article's author's personal opinion.

    Background

    Naming Things

    Phil Karlton once said

    There are only two hard things in Computer Science: cache invalidation and naming things.

    This is a fairly famous quote among software professionals and it refers to the difficulty in naming variables, functions, classes, projects, teams, and pretty much anything else requiring a moniker in software development. Naming things well is an incredibly important skill because it affects the way we think about the problems we’re solving and the code we’re writing.

    Surprisingly, there is comparatively very little written about naming things in computer science outside of academic papers and (probably) diatribes in company Slack threads. Tom Benner addresses this gap in the literature by writing a brief and enjoyable book on the topic.

    The Author

    I had not heard of Tom Benner before but while researching this article I found his website, which led me to his GitHub showcasing a few highly-starred repositories and some non-trivial contributions to the Kubernetes repository. Between that and his LinkedIn profile, I think he has the credentials to be considered someone worth reading.

    Ironically, if I’m reading it right, he added the current-context command to kubectl config. I say this is ironic because I never liked the word “context” for what this tool does and I’m about to recommend his book about naming things.

    Overview

    This is the first book I’ve ever read that tackles the topic of naming various things in day-to-day programming. The book intentionally avoids topics like case or naming projects or teams, narrowing the scope of the book to variables, functions, and classes.

    The book identifies four attributes of a name that should be considered when naming things: understandability, conciseness, consistency, and distinguishability. The book starts by introducing the challenges in naming things, then defines the four name attributes, and ends by providing examples for applying the attributes.

    Review: Great!

    I really liked this book. I read it on my iPad so I’m not exactly sure how many “pages” it is, but I read most of it on a 2-hour flight and finished it up the next morning in about an hour, so it’s a very quick read compared to other books in our field.

    General Notes

    I think tech books often present their ideas dogmatically and with little room for nuance. However, I think that this topic necessitates a bit of dogmatism, and the author does a pretty good job of being prescriptive when necessary but leaving wiggle room when appropriate.

    There was only one thing I disagreed with in the book. In the chapter about conciseness, the book advocates not including “how” a method is accomplished in its name and favors configuration arguments instead of multiple method names. The example given includes a CSV processor that can either process a CSV serially or in parallel processes. Specifically, the example given is:

    # Bad
    csv_processor.process_in_parallel(rows)
    csv_processor.process_in_serial(rows)
    
    # Good
    csv_processor.process(rows, in_parallel=True)
    csv_processor.process(rows, in_parallel=False)

    I personally disagree with this specific example because I believe the implementation leads to a method that is too big (the body having code for both kinds of processing) or a method that is named inaccurately (it’s called process but it actually determines “how” to process the CSV and then calls the appropriate private method, presumably). In my opinion, I think the solution to this particular example is to have a CSVProcessor parent class and two child classes called SerialCSVProcessor and ParallelCSVProcessor that each implement a process method.

    This is a super minor disagreement though, and the author has quite a few more years of experience than I do, so I would likely defer to their judgement if they were on my team. In fact, I will look for areas where this rule can apply and see if I still disagree with its sentiment.

    Required Reading for Newly Minted Developers

    I believe this book would be most helpful to a developer in their first year on the job. The concepts are neither too abstract nor too technical, and a new developer has enough experience to understand and appreciate the content of the book. I also think most seasoned developers would agree with the majority of the content, so it would be helpful for new developers to get on the same page with their more senior colleagues.

    Conclusion

    Pros: Very useful information concisely delivered.

    Cons: None really

    Overall: The first book on the topic at this level of accessibility. It’s useful to senior and junior developers alike (although moreso for the latter). Also, the references were quite interesting if you want a deeper dive on the topic.

  • How to Use C Functions in Python

    How to Use C Functions in Python

    Did you know you can write functions in C and then call them directly from Python? Isn’t that cool? Let’s skip all the background and the “why would I ever need to do this” for now and just dive on in to the code!

    Read more: How to Use C Functions in Python

    Originally posted here on dev.to

    First, the C Function

    To demonstrate, we’re going to write a program in C to find the factorial of a number. If you don’t remember factorials from high school, here’s an example:

    4! (read four factorial) = 4 * 3 * 2 * 1

    That is what our C program is going to do. Fire up a text editor and lets crank this function out:

    long factorial(int user_input) {
      long return_val = 1;
      if (user_input <= 0) {
        return -1;
      else {
        for (long i = 1; i <= user_input; i++) {
          return_val *= i;
        }
      }
      return return_val;
    }
    
    int main() {
      return 0;
    }

    We are defining a function called “factorial” which will return a “long.” We’re using long instead of int because factorial functions can return some pretty big numbers.

    Next, we’re declaring and initializing return_val which we’ll use to return the value of the calculation.

    Now, the if statement is ensuring the number passed in by the user is positive, and if not, to return the value of -1. We’re returning -1 because later, when we wrap this function in Python, we’re going to know that getting -1 back from the C function probably means there was bad input.

    If the number returned is greater than 0, we enter our loop in which we use an iterator, i, and multiply our return_val variable by it until i is equal to the number passed in by the user. Basically, this loop is saying:
    n! = 1 * 2 * 3 * 4 ... * n

    The final part, with the int main() is to appease the C compiler when we turn this into a .so file. I may be mistaken, but I’m pretty sure this part is necessary even though it doesn’t do anything. If anyone knows any better, please feel free to mention so.

    The Pre-Python Part

    Now that our C is written we have a couple things to do before we write the Python bit. First, save the .c file. I called mine cfactorial.c. Now, we have to turn this into a “shared object” file. In Linux, the command to do so is this:

    $ cc -fPIC -shared -o cfactorial.so cfactorial.c

    This particular command will make a cfactorial.so out of my cfactorial.c file. Now, to the actual Python

    The Python Part

    Almost done! Fire up that text editor again and lets script out some Python. First, we need to import the ctypes module. Then, if you’re anything like me, you’ll want to put the absolute path of the .so file into its own variable. So the top of my pyfactorial.py looks like this:

    from ctypes import *
    
    so_file = '/home/ewhiting/cstuff/cfactorial.so'

    The next thing we want to do is create our cdll object out of our previously created .so file. So, after the so_file variable assignment, put:

    cfactorial = CDLL(so_file)

    Now, technically at this point you can start messing with calling the C function in the Python script by running python in the command line but lets be a little responsible first. Before we play with it some more, lets wrap our C function in a Python function. After creating the cfactorial variable, create the following function:

    def factorial(num):
      c_return = cfactorial.factorial(num)
      if (c_return != -1):
        return c_return
      else:
        return "C Function failed, check inputs"

    Save this file as pyfactorial.py. Altogether, it should look like this:

    from ctypes import *
    
    so_file = '/home/ewhiting/cstuff/cfactorial.so'
    cfactorial = CDLL(so_file)
    
    def factorial(num):
      c_return = cfactorial.factorial(num)
      if (c_return != -1):
        return c_return
      else:
        return "C Function failed, check inputs"

    Note, the way to call functions inside the imported C shared object file is by saying <CDLL Object>.<function name from C code>(<parameter>). Easy!

    So basically, any time we want to use that C function within Python, we call the factorial function which will run the C function with the parameter passed in by the user and evaluate the result. If the C function returns -1 (remember we put that in there), the Python script knows that there was a problem. Otherwise, it will return the number. Lets try it out! Fire up your terminal and start python

    >>> import pyfactorial as pf
    >>> pf.factorial(5)
    120
    >>> pf.factorial(10)
    3628800
    >>> pf.factorial(-4)
    'C Function failed, check inputs'

    Ta-da!! That’s the basic idea behind using C functions in Python. This is definitely a tool worth having. Apply all your other programmerly knowledge to making awesome functions and features, and let me know if you have any questions.

    For my of my writing on C, check out my series on pointers starting with a gentle introduction. If you want reasons to not ever write C in your life, check out my article about why you shouldn’t learn C.

  • An Introduction to Software Architecture

    An Introduction to Software Architecture

    Software architecture is the concept of how a software project is structured; a holistic view of the entire system including class hierarchies, interface design, even deployment patterns. A system’s architecture impacts nearly every facet of interaction with that system, from end users to the developers that build and maintain it. When you understand your system’s architecture, you’ll develop an intuition for estimating the time it will take to build new features, where bugs may be hiding in the code, and how to influence the performance of your system. In order to give you the tools to study the architecture of the projects you’ll one day be contributing to, this article lays a foundation for the most important concepts related to the topic, namely architectural patterns, reusability, quality attributes, and tradeoffs.

    Read more: An Introduction to Software Architecture

    Defining Software Architecture

    It is difficult to find a helpful and exact definition for software architecture that accurately communicates its importance and prevalence. One simple definition of software architecture might be “the structure of a software system.” But that definition lacks the gravitas the concept deserves.

    More colloquially, you will often hear people define software architecture as “the stuff that’s expensive to change later.” Such a definition, while true, doesn’t really tell us much about what it is. For example, the programming language in which a system is written is expensive to change, but so is the application’s project management software (for example, moving from internal documents to a professional project management system like Jira is very expensive). These are both expensive things to change, but only the programming language is a facet of software architecture.

    In truth, software architecture means different things to different people and there is no universal definition that all developers will agree on. To some, software architecture is similar to class design in that it models the relationships between classes throughout the entire system.

    Others extend the concept to include not only class relationships, but also the infrastructure upon which the system runs such as the type of database a system might use or the type of web server.

    An official definition for software architecture exists in a document from the International Organization for Standardization (ISO) in conjunction with the International Electrotechnical Commission (IEC) and the Institute of Electrical and Electronics Engineers (IEEE) called ISO/IEC/IEEE 42010:2022 – Systems and software engineering – Architecture Description. This standardization document describes software architecture as:

    [The] fundamental concepts or properties of an entity in its environment and governing principles for the realization and evolution of this entity and its related life cycle processes

    ISO/IEC/IEEE 42010:2022 – Systems and software engineering – Architecture Description

    “Environment” in that definition is later defined in the same document as:

    [The] context of surrounding things, conditions, or influences upon an entity

    ISO/IEC/IEEE 42010:2022 – Systems and software engineering – Architecture Description

    Personally, I like this definition and I think it serves the purpose of defining architecture well enough for newcomers to the topic. For the purposes of the rest of this article, however, we will only learn about two facets of software architecture: architectural patterns and components, and reusability. These are the most significant and impactful parts of software architecture as well as the easiest to explain. Let’s go over what each one means.

    Architectural Patterns and Components

    Architectural components are layers of functionality within a system that are responsible for a specific system-wide task like accessing a datastore or routing application requests. Architectural patterns refer to the ways in which these different components work together to achieve the system’s purpose.

    The idea of architectural patterns is somewhat similar to that of design patterns; like design patterns, architectural patterns are made up of components accomplishing specific tasks that are pieced together to accomplish broader tasks in the most effective way possible. Another way in which architectural patterns are like design patterns is that there are many well-documented patterns to choose from, each with their own strengths and weaknesses. However, unlike design patterns, architectural patterns have system-wide impacts.

    As an example, consider the factory method design pattern which creates instances of objects based on a given context. This pattern’s components work together to accomplish a very useful task, but outside of that task, it has little—if any—impact throughout the system. While architectural patterns are also made up of multiple components, each with a specific responsibility, the scope of that responsibility is much larger and less specific than those of a design pattern’s responsibility.

    It’s useful to think of the components of an architectural pattern as self-contained collections of code, interfaces, and possibly infrastructure. This is a level of abstraction higher than the idea of components in a design pattern because design pattern components only consist of code. As an example of an architectural component, consider the model-view-controller (MVC) pattern. In this architectural pattern, the model component (more often referred to as the model layer) consists of not only class definitions, but also the underlying datastore—like a database—of the application. It’s important to understand this idea of components at the architectural level because components are what make up architectural patterns and influence their strengths and weaknesses.

    Reusability

    On the topic of software architecture, reusability refers to a component’s suitability for handling a specific system-wide task. To use the MVC architecture example again, the architectural component called the controller layer is responsible for handling application routing tasks. Most of the time, MVC is used in web applications, so the controller layer is responsible for handling HTTP/HTTPS requests from browsers, sending them to the appropriate code hosted on a web server, and sending the response back to the browser. Once the controller layer is built, we don’t have to write it again. This means that no matter how many features we have, the controller layer is reusable for handling all the application routing needs of that feature. This saves the development team weeks of time by sparing them the task of writing HTTP routing code every time they want to introduce a new feature into a system.

    By now we should be starting to understand what the concept of software architecture is getting at. Now, let’s talk about why software architecture is worth knowing about.

    Why Architecture Matters

    Software architecture is the “big picture” of a software system and therefore permeates every aspect of the code. Consider an obvious metaphor: the architecture of a building. The building’s architecture dictates everything about the building that makes it useful; how many people it can hold, where the emergency exits are, how to move from one area to another, the functions of different parts of the building, how future contractors might make upgrades to the building, even how the building affects the environment around it.

    Additionally, a building’s architect has to make decisions about what attributes of the building are most important, which are nice to have, and which are not important at all. For example, an architect may be told to make a building with the primary goal of keeping operational costs low, even if the building isn’t very aesthetically pleasing. In this case, the architect will decide that, even though floor-to-ceiling windows on every floor would make for a very nice-looking building, it would be terrible for operational costs because the sun will heat up the building during the day and the people inside will have to turn up the air conditioner. In this case, the architect will have to opt for smaller windows throughout the building and perhaps even exterior walls of concrete to absorb the heat.

    Likewise, software architecture dictates everything about the system that makes it useful, and software architects have to make decisions that sacrifice one thing for another. In software architecture, these “things” that are sacrificed are called quality attributes, and the decisions about which quality attributes to prioritize are called tradeoffs. Let’s talk about both of these concepts, starting with quality attributes.

    Quality Attributes

    Quality attributes are the non-functional attributes of a software system. In this case, an attribute is non-functional in that it doesn’t do anything. For example, suppose we’re building a calculator app. Being able to do addition is an example of a functional attribute. On the other hand, how quickly the calculator can produce the results of adding two numbers together is a non-functional attribute. Saying the calculator is fast or slow is a comment on one of the calculator’s quality attributes. Aside from the power and modernity of the physical infrastructure upon which a project runs, nothing impacts a system’s quality attributes more than its architecture. Let’s define some of the most important quality attributes, what they are, and how architectural decisions affect them.

    Reliability

    Reliability is a quality attribute that defines a system’s ability to perform its tasks under some given conditions for some given amount of time. For example, suppose we’re building a web server upon which someone to host their website. Say one of our users has their homepage, index.html, on our server. We expect that any time someone in the world sends and HTTP request to our server requesting that index.html file, our server will properly send the page back to that person. Occasionally, due to any number of reasons, our server will fail to send the requested index.html page and will instead send the user a 404 – Not Found response code. This is considered a failure.

    The probability of our server correctly returning the requested index.html page at any given time under some given condition is a measure of reliability. For example, suppose we send one thousand requests for the index.html page over the span of fifteen minutes and our server correctly sends back the index.html page 991 times, the reliability of our server with at one thousand requests over fifteen minutes is thus 99.1%.

    One way in which software architecture affects reliability in this case is how we decide to handle request failures on the server. When the server receives a request for index.html and for some reason cannot find it, instead of sending back a failure 404 message to the user, we could tell the server to try again up to five times. A very simplified version of how this might look in the code is as follows:

    def handle_request(file_name, retries=0):
      try:
        resource = open(file_name)
        # File was found, send it to the requester
        send(resource)
      except:
        # File not found, try again
        retries += 1
        if retries < 5:
          print(f"Resource not found, retry number {retries}")
          handle_request(file_name, retries)
        else:
          # Couldn't find file after 5 attempts,
          # send 404 error message
          send(404)

    In the code above, we wrote a function that handles a request for a resource like index.html and sends it back to the user (note, the send method is not defined in this code, this is just for example purposes). We put the open method inside of a try block and use the except block to handle any failure to find the resource. This will help alleviate any random failures that might happen by simply telling the server to try again. However, we also prevent any infinite loops from happening by telling the server to quit trying to find the requested resource after five tries. If the max number of retries is reached, the server sends the 404 response code. Now if we were to test the server again with a thousand requests for index.html, we may see improvements; perhaps the server eventually sends the requested resource 999 times, increasing our reliability measure to 99.9%.

    Availability

    Availability is closely related to reliability in that it measures the probability that a system will be available to perform its task at any given time. This measure is directly impacted by the system’s reliability, but also takes things like maintenance downtime into account. Availability over some period of time is measured as that period of time, minus the amount of downtime and then divided by the period of time being measured. For example, if we measure daily availability and find that the system is unavailable for about five minutes every day, the availability of the system is 1440 (the number of minutes in a day) minus 5 (the number of minutes the system is unavailable), all divided by 1440, or 99.65%.

    You may occasionally hear people refer to their system availability in terms of nines. The system in our previous example has “two nines” of availability because the number 99.65 has two nines in it. These numbers are often used in service level agreements (SLAs), contracts that system developers have with their customers agreeing on how much availability they can expect from the system. Five nines (99.999%) of availability is considered very high availability as it allows for five minutes and 15 seconds of downtime a year.

    Software architecture impacts the system’s availability by defining how system maintenance is conducted. For example, if an application’s database goes offline for some reason, can the application still be accessed while we figure out how to get the database back up and running, or does the whole system have to come offline? If we wrote our components to be reliant on the database, the whole application will likely be unavailable while the database is down. If it takes us five minutes and 15 seconds to get the application back online, we can’t have any more downtime for the rest of the year if we want maintain five nines availability.

    Scalability

    Scalability refers to how a system’s performance and cost increases and decreases with demand. For example, imagine our system has a baseline number of 500 users at any given time, but for some reason, one day we have two-thousand users for a couple of hours. In order for the system to perform identically for all users, we’ve programmed it to spin up a duplicate server every additional 500 users to handle the extra demand. Also, we’ve programmed the system to spin down those servers as the spike in demand tapers off. This means that both cost and performance increase and decrease based on the demands placed on the system.

    Scalable systems are created by writing modular code. As a senior engineer or the architect on your team, you enforce code modularity by ensuring that the components that rely on infrastructure can handle that infrastructure being duplicated. For example, hardcoding the IP address of our servers in the code would not be conducive to scalability since new servers would have different IP addresses.

    Security

    Security is a quality attribute that refers to a system’s ability to protect data from unauthorized access, prevent users from accessing a higher level of privilege in the system than they need, and much more. Security is a broad topic and there’s a ton of information to know about it, so this section will cover only a very small subset of how it applies to software architecture.

    When you’re architecting a system, you have to think about how data will be stored and if it’s allowed to travel networks unencrypted. For example, when transmitting data from a web server to a user’s browser, the data is accessible to people (network administrators, people using packet sniffers, and so on). In fact, it’s good practice to assume that any data traveling over a network is being read by nefarious people. We might not care who sees some data if it’s something innocuous like a person’s shoe size, but often we transmit data like names or credit card numbers over a network and we definitely don’t want people reading that, so we encrypt it.

    Encryption, like security, is also a broad topic but I wanted to briefly define what it is in case you’ve not heard of it before. Basically, encryption is the process of scrambling data so that it’s not human-readable, and the only way to unscramble it is with a special decoder called an encryption key. Generally speaking, only the machine receiving encrypted data has the appropriate encryption key, so even if that data is captured by nefarious people, it is no good to them because they can’t read what it says.

    The system’s data encryption policy is an important architectural decision because data is much bigger when it’s encrypted than when not. As a consequence, encrypted data takes slightly longer to travel from point a to point b in a network. It’s not a huge difference, but as your system grows, the amount of data flowing through the system will impact the system’s performance. Figuring out what data needs to be encrypted is just one small facet of security minded software architecture.

    Maintainability

    The last quality attribute we’ll discuss is maintainability. A system’s maintainability is a qualitative measure of how easily developers can add features, fix bugs, or tweak performance. Maintainability isn’t something we can measure with a formula; it is more of a general feeling about how easy the system is to work with from a developer’s perspective. Many of the topics you learn as you become a more senior developer are techniques for writing maintainable code. For example, making smart decisions about class design makes future work easier to do because intuitive class design lowers cognitive complexity. Likewise, using design patterns where appropriate along with naming their components descriptively helps developers navigate the system easily, reducing the time needed to add a new feature or fix an overlooked bug.

    Software architecture impacts the maintainability of the system because every architectural decision influences how code must be written. As an example, a microservices architecture pattern is useful for separating business concerns into individually deployable components. This means that, as a developer, if I get tasked with writing a feature for the marketing team, I know exactly which component I need to work on. However, if I’m working in a system built on the MVC architectural pattern, I may have to work with several components to deliver the required feature.

    Architectural Tradeoffs

    Now that know what quality attributes are and have seen some examples, let’s talk about architectural tradeoffs. As a senior developer or software architect, you’ll often be faced with building features that have competing priorities. Consider the example we used when talking about reliability. Adding the try … except blocks to the server’s code allowed us to increase the server’s reliability, but it most likely will make the server a little slower. This is because exception handling is very slow when exceptions are raised, meaning that every now and then, a user will have a slightly slower experience than if we got rid of the server’s exception handling altogether.

    This blog's article on exception handling in Python

    Deciding which is more important, the speed of the request response or the reliability of the server, is an architectural tradeoff you will have to make. This decision will be influenced by many factors such as the kind of application you’re building, the needs of the users you’re building it for, and much more. There are no universally correct answers to architectural tradeoff questions, they always depend on the context in which you’re working.

    One of the tradeoffs you’ll have to think about quite often is security. As we discussed in the quality attribute section, something like encryption can have a significant impact on your system’s performance. As an architect, you’ll have to decide what needs to be encrypted and what doesn’t. If you’re working in the defense industry, you’ll likely have to encrypt every bit of data that travels over a network, causing your system to perform slowly. However, if you’re writing web-based browser games that don’t require a login, you likely don’t need to encrypt anything. The kinds of tradeoffs you make will depend on the industry in which you’re working and the type of system you’re building.

    One thing that will impact your architectural decisions and tradeoffs is the due date of your projects. For example, if you have a year to build a back-office application for a small company, you will likely have maintainability as a high priority so that you can troubleshoot problems easily and add new features to the system upon request. Conversely, if you’ve been tasked with building the same system in a month, you will not take the time to consider maintainability. This is because building maintainable systems requires extra care and planning, time you don’t have with such a quick due date.

    Yet another decision about architecture you’ll have to make is whether to build a component yourself or use a third-party component. Third party components, sometimes called commercial off the shelf (COTS) software, are components built by other organizations that you usually have to pay for. COTS software is supposed to require very little work for a developer, and sometimes includes 24-hour support (that is, you can call someone for help if the COTS software isn’t doing what you want it to). One example of a homegrown vs. COTS solution is the decision on how to monitor your system’s performance. Quite often, it’s necessary to watch your system’s performance such as CPU or RAM usage, network latency, and much more. Some teams find it best to write their own software for monitoring system performance while others buy performance monitoring software from other vendors. The decision on which way to go will depend on a lot of things such as staffing (you need more developers to build and maintain such software) and cost (some performance monitoring applications are very expensive), and whether or not the COTS software can easily service your needs.

    Finally, perhaps the most important consideration when making architectural tradeoffs is cost. You and/or your company have a finite amount of money set aside to build whatever system you’re working on. How you use that money to accomplish the goals of the system often includes making several architectural tradeoffs. For example, do you want to pay for a database management system like Microsoft SQL Server so that you can have 24-hour support ready to help you if something goes wrong, or do you prefer using an open-source option like MySQL or Postgres for free, but which do not have a helpdesk for you to call? The answer to that question will depend on many factors. For example, are you a lone developer for the company you’re building this system for, and the company needs the application to be running all the time? If so, you might want to opt for the SQL Server option since you may need help one day. Conversely, are you building an open-source project for people to share recipes with each other? If so, your budget is probably small, and your users won’t lose millions of dollars if your database stops working. In such a case, self-hosting a free database system like MySQL should be fine.

    The responsibility for making the decisions on these tradeoffs will fall on you more often as you become more experienced in whatever tech community you’re a part of, be it an open-source organization or a company. Understanding the overall goals your system and how your organization plans to use and support it will be an important step towards making good architectural tradeoffs.

    Conclusion

    This article introduced an important concept in your path to becoming an experienced and professional-grade programmer: software architecture. Software architecture can be defined in a number of ways, but it’s ultimately the process of developing components and fitting them together in the most efficient way relevant to our system’s goals. We learned about those components and how they form architectural patterns. Then, we learned about quality attributes and how they’re impacted by software architecture. Learning about quality attributes is a prerequisite to learning about architectural tradeoffs, the decisions you must make when developing systems. Hopefully now you have an understanding for what software architecture is and why it’s important.

  • How to Debug Code (with Python Examples)

    How to Debug Code (with Python Examples)

    Often in your programming career, you will inadvertently write flawed code that introduces some fault into your codebase. These faults are called bugs and the activity of fixing bugs is called “debugging.” Of course, as developers, we try to write correct code every time, but writing bugs is simply a fact of life for programmers. The ability to diagnose and fix bugs is the mark of an experienced and talented developer and is foundational for advanced troubleshooting. In this article, we’ll go over some techniques for effective debugging and introduce you to PDB, the Python debugger. At the end of this article, you’ll be ready to not only troubleshoot and fix your own code, but to diagnose and debug existing code as well.

    (more…)
  • [Review] Managing Technical Debt: Reducing Friction in Software Development

    [Review] Managing Technical Debt: Reducing Friction in Software Development

    In today’s post, I review Managing Technical Debt: Reducing Friction in Software Development, by Phillippe Kruchten, Robert Nord, and Ipek Ozkaya.

    (more…)
  • Python Exception Handling and Customization

    Python Exception Handling and Customization

    Like bugs, exceptions are inevitable when developing software, especially as the complexity of that software increases. Sometimes exceptions are surprising, other times we can anticipate them coming. How a program responds to the occurrence of exceptions is called exception handling, and as programmers, we can define and customize exception handling. In this chapter, we’ll learn what exceptions are, how to handle them, and how to make our own.

    (more…)
  • Test-Driven Development with Python: a Primer

    Test-Driven Development with Python: a Primer

    Making sure the software we build works the way we (and our customers) want it to work is called, unsurprisingly, software testing. Software testing is an enormous topic; indeed, there are entire books, courses, conferences, academic journals, and more about the topic. One can even make a career out of testing software. We couldn’t possibly scratch the surface of the complexity involved in software testing in this article, so we’ll only focus on a topic most relevant to us as programmers: test-driven development.

    (more…)
  • Understanding INNER, OUTER, LEFT, and RIGHT Joins in SQL

    Understanding INNER, OUTER, LEFT, and RIGHT Joins in SQL

    One of the first and most common things newcomers to SQL struggle with is how each JOIN is different from the other. In this article, I’ll explain the differences between the inner vs outer JOIN and left vs right JOIN in SQL using examples from each.

    (more…)
  • Improve Knowledge Sharing and Productivity with Rubber Ducking

    Improve Knowledge Sharing and Productivity with Rubber Ducking

    Rubber ducking is a software development technique in which you communicate some problem you’re dealing with in an attempt to gain better clarity or understanding of the problem. The communication can be verbal or written, and the audience can be real or imaginary. In this article, I give you some tips for facilitating a recurring rubber ducking meeting with your team.

    (more…)