

Develpreneur: Become a Better Developer and Entrepreneur
Rob Broadhead
This podcast is for aspiring entrepreneurs and technologists as well as those that want to become a designer and implementors of great software solutions. That includes solving problems through technology. We look at the whole skill set that makes a great developer. This includes tech skills, business and entrepreneurial skills, and life-hacking, so you have the time to get the job done while still enjoying life.
Episodes
Mentioned books

Apr 14, 2021 • 15min
Leverage Your Language - Use What It Gives You
An often overlooked facet of object-oriented design is how to leverage your language to speed development. Every coding language has features and strengths to make your design or implementation better. We should not design to a language initially. However, we can tailor our implementation and detailed design to take advantage of these features. Leverage Your Language to Boost The Implementation Languages are implementation tools. Therefore, they come into play during that phase. Nevertheless, you can leverage your language as you consider the details of the design. Some approaches will either be simple or complex based on the language. We can include the concept of interfaces or multiple-inheritance in a hierarchy. That includes more granular steps like patterns built into the language. We often see these in the form of iterators or factories. Keep Up With Changes While the initial design should leverage your language, enhancements and upgrades should incorporate the same. Be aware of the changes to a language or environment over time. That includes fixes, tweaks, and new versions. A problem that may have been overly complex to solve in the past could be a "freebie" solution now that it is handled in a single line of code. Languages evolve and often do so with a focus on eliminating weaknesses. That is due to developers suggesting features based on their pain points. We often see a language that is limited in use during early versions become more general-purpose over time. The developer is the customer, so the software team or company will cater to the developers. The things that are difficult to use early on will be simplified or greater support provided. Under The Covers The changes to be aware of include those that are "under the covers." These types of changes are much more implementation-impacting. However, version upgrades have been known to make vast improvements in persistence, multi-threading, and thread-safety.

Apr 12, 2021 • 16min
Finding Balance In Your Object-Oriented Design Complexity
There are many areas of life where moderation is required. Object-oriented design complexity is one of those situations. We can create a solution that is highly granular and flexible or one that is monolithic and simple. However, as is often the case, the best solutions find some middle ground that keeps our application flexible and limits complexity. A Balanced Object-Oriented Design Every solution is unique and has its own constraints and requirements. Nevertheless, we have guidelines to avoid a solution that is either over-simplified or over-engineered. They may appear obvious but are often overlooked. Isolate unique functionality - we will not get much re-use from it. Look for repeated actions or manipulations. Focus on a unit of work. Do not break down a problem that is not improved by doing so. When we walk through the steps of our solution, we should be either to easily see how to solve the related problems. Those related problems are often a pointer to our methods and classes. We want to break it down into easily solvable pieces and then stop. There is no need for our object-oriented design complexity to rise to the level of micro-managing the solution. Re-use Is A Many Splendered Thing When we are thinking about code re-use and the related work to make a solution generic, there should be multiple uses. This should be more than two occurrences or at least a vision of the code being reused many times. That is where we gain back the extra time spent in creating a generic solution rather than brute force. Our design should take into account that the direct and simple approach can also be the best. We do not get points for an elegant solution in most cases. Simple First The best designs start with a simple solution. It is easier to make our software more complex in a future iteration than simplifying it. We also will have lower maintenance costs associated with a less complex solution. These benefits add up and should bias us away from over-architected solutions. Not only are there no gains inherent in a complex system, but there are also many assumed costs. These include a slower solution, one that is harder to maintain, and a longer ramp-up time for new/additional resources. Therefore, simple is better than complex when possible.

Apr 9, 2021 • 14min
Documentation Of Our Object-Oriented Solution
It is hard to think of a facet of software development that programmers dislike more than documentation. This task is almost always pushed to the end, and shortcuts are used wherever possible. However, we also are quick to complain about a lack of documentation when we pick up others' code. Maybe we need to give so we can get. that is the focus of this episode. Documentation Begins With A Signature All good documents start from an outline. There is either a written or mental plan that the author follows. In software, this objective comes from the signatures of the class and methods. We have a set of details we must provide for every method out there. What are the parameters supported? Are there constraints for those parameters? What is returned? Are there exceptions or error-handling that needs to be documented? Do we require other classes or libraries? Are there side effects? That is a sizable list when you consider that many methods are documented in a sentence or two. While documentation can be exact and concise, that is not always best. Using Documentation Generators Consider a method: integer DoSomething(parm1, parm2) Here is the typical documentation: DoSomething returns an error code or 0 on success. The first parameter is a number that tells which type of processing is needed (trim, pad, compress, encrypt). The second is a string that is processed. I am being a bit obtuse in the above comment, but not far from what most automated tools generate. That should be a start for our documentation and not the final version. Add Color Commentary We need to add details and specifics to our documentation. This includes things like validations, side effects, and avoiding magic numbers. Therefore, a better approach to the above would be something like this. DoSomething returns a 0 for success, -1 if the action is invalid, -2 if the string is null, -3 if the action fails. The first parameter (parm1) is an integer with a value of 0 to 3. This tells the method the action to perform. 0 - trim the string 1 - pad the string (left pad spaces to make the length 20) 2 - compress the string 3 - encrypt the string The second parameter (parm2) is the string the action is performed on. The resulting string is set in the instance and can be viewed through the getValue method (provide a link to that method if possible). There is minimal formatting in the example above. Nevertheless, notice how much more information we have now provided. There is no guesswork, and our expectations are properly set. Thus, we have a document that is useful and will make the next developer happy to work with our code.

Apr 7, 2021 • 15min
Test-Driven Development - A Better Object Oriented Design Approach
Testing and Design are often at opposite ends of the software development life-cycle. However, test-driven development is an excellent way to drive our design. It can help us build better classes and improve re-use. This episode focuses on TDD and how it can point us to full-featured classes with better error handling and messages. Test-Driven Development For Design We previously looked at unit testing and class-level quality assurance. While those are important tasks once we get the class implemented, they are better incorporated into the design process. When we do, it ties our testing (validation) more directly to requirements. Thus, we have goals for each bit of code to achieve. We drive our implementation by the tests we need to pass. This approach is not the only way to do so. However, it does translate requirements to the implementation. It also can help us find gaps. Testing Responses Testing centers around the responses and behaviors of our classes and methods. Thus, our class must support a message, exception, or value that the test is looking for. In the case of exceptions, I find testing a key indicator of where we need to support them or report errors. That is because a unit test has this basic format. Make a call. Gather results. Compare results to a set of assumptions. That third step will highlight areas where we have the proper amount of error handling and messages supported. A Simple Example Let's consider a simple example to show how this works. In our example, we have a method that takes two numbers, adds them, and returns the sum. The requirements tell us we need to verify the sum is correct; we warn the user if a negative value is sent. Finally, we need to warn the user if they do not provide a number. If we implement first and then test, we can easily forget the latter requirements. We will probably add the numbers and return the sum. It is not until we get to testing that we will realize we have some exceptions handle. If we take the test-driven development approach, then handling these exceptions and related messages will be in our minds from the start. That can heavily influence the kinds of things we want to support and return as we design our class.

Apr 5, 2021 • 14min
Testing Object-Oriented Code - A Better Design Approach
We have spent a lot of time on the core concepts of object-oriented design and programming. However, testing object-oriented code is at least as essential to our practical approach. We need to ensure that our code is written with a quality that makes it worth re-using. Otherwise, it is a better use of time to write new code instead of wrangle low-quality code with the idea of re-use. We have all seen these situations where it is easier and faster to start from scratch than building on previously written code. Testing Object-Oriented Code Through Design We will focus on test-driven development in the next episode. However, our implementation and approach to a solution can be improved with the inclusion of unit tests. There is a level of expectation that unit tests point to. When we start to code these, the gaps in our return values and messages become apparent. Thus, we get the benefits of tested code and a more thorough design. Unit and Class-Level Testing Unit-testing typically is at the method level. The good news is that we can connect a few of these and easily shift to class-level tests. These are important steps towards building a highly re-usable class. We will show (through successful tests) that our methods work individually and as part of the class system. While interactions and interfaces for our class are important, it is more valuable to have a class that is a solid ecosystem. The Goal Is Re-Usable All of these points lead us to how confident we are in our code. If we have a low-confidence, then why would we re-use it? In those cases, we are better off writing "one-off" code that avoids the costs of object-oriented. There is no benefit in staying on the fence. Either embrace OOD and testing object-oriented code for re-use or write something quick-and-dirty. This concept applies throughout the real world. There is no sense building on a flimsy foundation.

Apr 2, 2021 • 15min
Destructors And Cleaning Up An Instance: Object-Oriented Design
When we get to the end of our use of a class instance, we need to clean up after ourselves. This is where destructors come into play. They are similar to constructors in a mirror fashion. While a constructor creates an instance, a destructor removes an instance. Destructors And Resource Release Many languages have a default destructor, much as they have a default constructor. These methods likely free up some pointers behind the scenes. However, we also have resources and properties or even notifications that may be needed as part of this process. That leads us to a process that effectively undoes the creation and initialization steps. We first release resources (reverse the initialization). Then we remove the instance from memory (reverse the constructor). The Important Properties The challenge with a destructor and cleanup is that many languages do most of this work for us. The tasks of memory initialization and pointer creation are done under the covers. Therefore, we can leave the default destructor as is and let the system manage those things. However, we do often have non-simple attributes like child instances and resources to release like file handles. These complex attributes and initialization steps are critical to review and address in our destructors. Keep It Simple With all of the above in mind, we also need to consider what happens when a destructor fails. Therefore, it is helpful to have a close or cleanup method that we can call separate from the destructor. It may seem like splitting hairs, but there is value in handling releasing resources separate from destroying an instance. This impact comes from destructors often being called more often than we think. A fat destructor can dramatically slow performance, just as we see in fat constructors. This multiple-step approach to cleaning up makes it easier to handle each step at a time. Thus, potential exceptions are addressed individually rather than treating them in a bunch. That smaller scope will help you avoid headaches and synchronization of getting the system cleaned up.

Mar 31, 2021 • 15min
Constructors And Initializers In Object-Oriented Design
When we create an instance of a class, we often need to do some initialization. Constructors and initializers achieve this goal. These are two different ways to get our data into an instance and are an essential part of our design. In this episode, we examine the design decisions we need to make as we decide how to use these common methods. Constructors And Initializers - The Distinction A constructor and an initializer method can be thought of in very similar ways. However, there is one particular trait that differs between the two approaches. A constructor returns an instance of a class, whereas an initializer sets values on an instance. This may seem to be a minor difference. It is not. There are situations where we need to create many instances of a class, and a "fat" constructor can lead to performance issues. The creation of an instance is an important operation. Thus, it is best to complete it in as short a time as possible. That is where an initializer comes in. We can create an instance quickly and then take our time populating that instance (i.e., initializing it). A No-Args Constructor Many languages include a default constructor for classes that takes zero arguments. This provides a quick way to get an instance. I personally find these important and highly useful in extending classes. The exception to providing a default constructor is when you need to provide limited instances or some level of instance management. Adding Parameters Once you start adding parameters to your instance creation, you should consider an initializer. That provides a cleaner way to create an instance and then populate it. You also give the developer the ability to "lazy load" data, for that instance. That is an important option in large systems and large classes. A good initialization routine also provides an opportunity for a mirroring cleanup method that makes it easy for a developer to control resource usage within classes. A few core parameters for an instance may be convenient, but once that list gets long, it can slow the process. It is better first to get a valid instance and then do something with it.

Mar 29, 2021 • 14min
Static Attributes And Methods In Object-Oriented Design
In this episode, we look at how to use static attributes and methods in our class design. These are important but can be over-used. They can even be a supported way to break the object-oriented nature of our program. We can "static" our way into a design that can not be extended. Therefore, we need to use these sparingly and with consideration about how they may limit our solution. Static Attributes It always helps to set the stage for these types of discussions. Static attributes and methods are not supported by all languages, nor is that always the proper terminology. For our purposes, a "static" is a class level value or function. Thus, there is only one version of these items. There is not a version on each instance. For example, a class-level attribute of "counter" will have the same value for all instances. When the counter variable is altered, all instances see that new value. As you can see, this approach will tie our instances together in a loose way. They are now linked through the static properties. The Singleton The software pattern "singleton" is an example where static values and methods are useful. They are almost required to implement this pattern properly. In this case, we have a single instance only for a class. This could be to provide global values or to limit access to resources. We might even blur the singleton into a limited number of instances. This option is a great way to have a small number of files open or database connections. A Static Anti-Pattern One of the uses of a static value is as a way to pass around or share that value. This approach can be used to avoid method parameters or a data store. While that is a valid way approach, it can be limiting and error-prone. A static value opens us up to the idea of race conditions. What happens when two bits of code try to update the value at the same time? It will be cleaner for us to pass values and limit access through properly designed methods, even though the direct approach is likely going to require less code. This feature provides us a lot of power. However, with great power comes great responsibility. Design responsibly.

Mar 26, 2021 • 16min
Software Design - Finding Balance With Coupling And Cohesion
We have spent a few episodes looking at cohesion and coupling. Now it is time to find balance in these two software design approaches. Each approach has strengths and weaknesses. Therefore, our best approach is to combine the strengths while offsetting the weaknesses. This best-fit approach is not as difficult to achieve as it may seem. Cache The Main Data The large object approach allows us to access data and methods quickly. We have a short path to get to these items as the layers of abstraction have been minimalized. The greatest value of this approach is when we have data or methods that are often used. There is little value to this efficiency for items we rarely utilize. For example, think about a daily report vs. a monthly report. The calculations that generate the monthly report can be less efficient and time-consuming because they are rarely utilized. A cost of 15 minutes for a feature is only paid once a month. The daily report has a higher value of performance. That same addition of fifteen minutes adds up to a loss of over a day per month. More iterations equate to a greater value for performance improvements. The A La Carte Approach We can push our primary data and features into a few large objects. That helps us with performance. However, it may seem overly taxing to a system to load all of the lesser-used data and methods in a single class. Instead, we can utilize smaller objects that can be quickly instantiated and freed for less frequent needs. The overhead cost of initialization and cleaning up these small objects is often a wash. We do not use them much. Thus, the slower performance has little overall impact on our system. A Flexible Software Design We pointed to flexibility as a strength of the coupling approach. However, we did not mention that coupling can be done with a combination of large and small objects. We see this thought process played out in patterns and frameworks like the flyweight and object factories. Even big things can have some small (replaceable) moving parts. That is the key to the blended approach. Keep the common things close and store the less-common features in easy-to-initialize small objects.

Mar 24, 2021 • 17min
Coupling - A Smaller Object Approach To Software Solutions
In object-oriented software, we talk about the concept of coupling. Put simply; this is a small object approach to our solution. It is the opposite end of the spectrum from large-object, or monolithic, designs. In my experience, a developer's bias towards cohesion or coupling often comes from their background. Those that have come from functional languages and platforms are more comfortable with cohesion, while those that built components tend to use coupling. Coupling Strengths It is hard not to point to flexibility as a primary strength of this approach. It stands to reason that the more points of adjustment available, the more flexible the solution. This example is easy to see in physical objects. Remove all of the joints in your body, and you will be less flexible. Likewise, return to our Lego example and consider what you can build with smaller as opposed to larger blocks and structures. Smaller Impact Another important strength of coupling is the ability to make changes with a smaller requirement of validation. Thus, we can make improvements or changes and have less testing required. This benefit can be critical to maintaining large systems without endless regression tests and unworkable release cycles. We do need to verify we have not introduced unintended changes. However, we have the ability to make tighter changes that are easier to debug. Where Does This Go? The challenge with coupling is figuring out what goes where. This conundrum is not different from the levels of normalization in a relational database. There are situations where an attribute or method has overlap in the classes that use or need it. This straddling of classes can make it challenging to place our bits of code properly. On the other hand, those situations often point us to where a third-class shared by the two primaries is the best solution.