Author Archives: Michael

Correctly applying the Open Closed Principle

Original Blog:https://blog.logrocket.com/solid-open-closed-principle/

For this post, I’ve decided to write about the application of the open-closed principle in programming, and how new features can be added to an existing piece of code without altering existing code. I’ll also be writing about the pitfalls of incorrectly using the Open-Closed principle in circumstances where it isn’t necessary or is excessive.

The Open-Closed principle is a principle in the SOLID foundation of principles, which is an acronym for the its tenants:

S – Single Responsibility

O – Open – Closed principle

L- Liskov Substitution Principle

I – Interference Segregation Principle

D – Dependency Inversion Principle

In the blog, “SOLID series: The Open – Closed Principle”, the author defines the open closed principle as dictating that a program be open for the addition of modules, classes and functions without changes to existing code being made. This can be done, among other methods, through the use of interfaces, which outlines which methods a class must implement without modifying the methods or class themselves, and abstract classes, which provide blueprints for future subclasses. By designing you program around the idea of shared methods within classes and inheritance, you ensure that bugs involving the code within those methods are few and far between. However, criticisms of OCP arise when the abstraction that results from repeated inheritance becomes cumbersome.

In the blog, the author states that many developers feel that the definition of the open-closed principle itself implies inheritance, leading some specific examples of improperly used OCP. The first one the author mentions is “Over engineered-abstractions”. This occurs when the amount of abstractions in a program is unnecessary, which could cause the program to become more complex than it needed to be. This can cause the codebase to become increasingly harder to understand to contributors, leading to possible bugs in the future of the program’s development. Another problem outlined by the author is the “interface explosion” problem. This happens when interfaces are overused in a codebase. The author mentions how the .net ecosystem suffers from this due to it’s reliance on dependency injection. When this is a problem, the codebase can become cluttered and dense.

In summary, the author explained the definition of the open-closed principle, then gave criticisms about the principle based on the environment in which they would be implemented, with inheritance and abstraction sometimes resulting in increase complexity and codebase clutter when implemented in environments that don’t necessarily need them. A thought I had about the material covered in the blog is how the use of the factory design pattern could help in cases of an “interface explosion”, since it would reduce dependencies required by the client for the code to function, and would reduce the amount of locations that would needed to be edited to add a new object of a certain type.

From the blog My first blog by Michael and used with permission of the author. All other rights reserved by the author.

Using immutability in conjunction with encapsulation.

Original Blog: https://blog.ploeh.dk/2024/06/12/simpler-encapsulation-with-immutability/

For this post, I’ve decided to write about encapsulation, and how implementing immutability in a program can make implementing encapsulation scalable and overall easier. I chose this topic because it seems like an interesting look at how concepts learned in a course I’m currently taking would appear in projects designed for a large user base.

Encapsulation is a widely used concept of object oriented programming, and for good reason. It allows developers to make efficient methods and functions with only the necessary amount of information, and also makes the scope of variables clear and defined, leading to debugging and problem identification becoming much easier than they would be without the use of encapsulation. A problem with encapsulation, however, is introduced in the blog “Simpler encapsulation with immutability”, by Mark Seemann. Seemann identifies a common difficulty encountered when implementing encapsulation on a large scale: How do we ensure that invariants are included? Invariants are defined as conditions within the program that remain true regardless of changes made to other areas of the code. This can include class representation, loop invariants, or other assumptions about the logic of your program. When implementing encapsulation at a large scale, it can be difficult to preserve this property in as many areas of the code as possible, with function variables taking different states throughout many points in the program.

A solution the author offers is to simply make the object immutable, guaranteeing that through whatever change it may see when the program is executed, it’s value can’t change. The author uses the example of making a program which models a priority list to model to difference in difficulty in preserving invariants, with the invariant being that the sum of the numbers in that list must be 0. Without defining each of the members of the list as immutable, it’s difficult to manually maintain at all times, while if the objects are immutable, you can guarantee that at no point will they not sum to 100.

In summary, the author outlines common problems developers have with implementing encapsulation while preserving invariants at large scales. He then provides the solution of immutability to ensure that at all times, objects desired to be invariants will be unchanging. Some thoughts I have about the blog are if making an object immutable could present some unwanted limit to the developer, and if that were to be the case, is there another solution which preserves invariants at a large scale without ensuring that a condition about an object only sometimes changes.

From the blog CS@Worcester – My first blog by Michael and used with permission of the author. All other rights reserved by the author.

REST API design philosophy and constraints

Original Blog: https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/

For this post, I’ve decided to write about what qualities a REST API platform ought to have in order for it to best serve the clients that depend on it. I’ve chosen this topic since it relates to what I’m currently studying in my software and design class.

As of today, REST API platforms are among the most widely used kinds of web interfaces. This is largely because of the versatility they offer in what can communicate with the API. Because of this, some key characteristics and qualities have been noted as essential for most, if not all, REST API platforms to ensure no problems are encountered in using the API. In the blog, “Best Practices for API Design”, by John Au-Yeung, the author details some practices he feels are the most necessary.

The first listed quality is the JSON compatibility, which includes being able to receive requests in JSON and being able to respond in JSON. The main reason for this quality being essential are the fact that most networked technologies either do or can use it with minor adjustments, compared to other frameworks, like XML, which would require the transforming of data so that it could be used. This would be very cumbersome for both the client and the maintainers of the API, and is less preferable than just using an already widely used framework like JSON.

The second listed quality is to make sure the endpoint paths of the API include only nouns describing what an http method acting on that endpoint would affect. For example, a path for using the GET method on a collection of articles would just be called GET /articles/ as opposed to GET /retrievearticles/. Since the http method will always add a verb to the path name, extra verbs that just reiterate what the method does can clutter the syntax of the endpoints. While this quality is maintained, endpoints and their functions are clear throughout the whole API.

The third and final quality mentioned by the author that I’ll include in this post is use of logical endpoint nesting when making paths for related endpoints. This quality says that if there is related or grouped data that can be accessed, structure the paths so that it’s clear that the information is related. The example the author gave was when designing a path to access comments on a specific article, the path should resemble /articles/articles:id/comments. With this structure, it’s clear to the user where the information contained in comments in coming from, and how that information interacts with articles and a specific articles id. This quality is important because it minimizes the amount of possible confusion a user may experience when accessing child resources.

In summary, of the qualities the author chose to emphasize, the ones that stood out to me the most were the logical nesting of paths and the compatibility with JSON. The logical path nesting seems like it could also prevent the problem of users suggesting errors in the API itself, when in reality they’re just confused by the unclear path structure, and JSON compatibility just seems too convenient to not have, and way too cumbersome to operate without, especially if your API is seeing use on a large scale.

From the blog My first blog by Michael and used with permission of the author. All other rights reserved by the author.

Software Development blog

This is my first post to my new blog.

From the blog CS@Worcester – My first blog by Michael and used with permission of the author. All other rights reserved by the author.

Dataflow testing overview

White box testing includes the use of all of a program’s internal structure to your advantage in the testing phase. A component of this internal structure that usually makes up a small percentage of the code body, but can contribute to the most amount of problem cases, are variable/data type declarations. Testing for these cases is called dataflow testing. In the blog, “All about dataflow testing in software testing”, by Prashant Bisht, the author details how dataflow testing would be implemented, and some examples of how it might look.

The implementation of dataflow testing, before any interaction with the code is done, is first executed with a control flow graph, which tracks the flow of variable definitions and where they are utilized. This type of organization allows for the first important internal component of dataflow testing, the tracking of unused variables. Removal of these unused variables can help narrow the search for the source of other problem cases. The second anomaly commonly testing for in dataflow testing is the undefined variable. These are more obvious than unused variables, since they almost always produce an error, due to the program relying on non existent data. The final anomaly tested for is multiple definitions of the same variable. Redundancy that can be introduced by this anomaly can lead to unexpected results or output.

Subtypes of dataflow testing exist, and are specialized to test different types of data. For example, static dataflow testing is the tracking of the flow of variables without the running of the tested code. This code only includes the analysis of the code’s structure. Another example, dynamic dataflow testing, focusing only on how the data relating to variables changes throughout the code’s execution.

To show how dataflow testing would work in practice, the author provided an example. This example concerns variables num1, num2, and num3. First, initialization of these variables is checked, i.e. if num1 is initialized as int nuM1 = some_int, the testing phase would catch this. Then, it ensures that the use of these variables don’t cause errors. This depends on program specifications, like if the program is meant to add each variable. The data flow is then analyzed, ensuring that operations including multiple variables are functioning properly. i.e. if num1 + num2 = result1, and num2 + num3 = result2, the dataflow phase would ensure that the operation result1 + result2 = result3 is functioning properly (though result3 being defined is a problem that would be handled by the first phase). The final phase is the data update phase, where the values of operations are verified to be what they’re expected to be.

From the blog CS@Worcester – My first blog by Michael and used with permission of the author. All other rights reserved by the author.

Benefits of gray box testing

In many software testing situations, white box testing, where the internal code of what is being tested is visible to the tester, and black box testing, where the internal code of what is being tested isn’t visible to the tester, are some of the most often used methods. However, integrating elements from both methods into a single testing method is slowly seeing more widespread use, also known as Gray box testing. In the blog “Exploring gray box testing techniques” by Dominik Szahidewicz, the author details different examples of gray box testing, and the benefits of those examples compared to the use of only white or black box testing.

Gray box testing has noticeable benefits that are absent from both white and black box testing. By the using the principles of white box testing (internal structure and design) and of black box testing (output without the context of internal structure), the testing process can be robust and able to account for any problem case.

A specific testing example where gray box testing can be implemented is pattern testing, where recurring patterns are leveraged to improve programs. With the use of gray box testing, the internal structure of the software can be related with the output to create more helpful and efficient test cases.

Another testing example where gray box testing can be implemented is orthogonal array testing, where data for testing is organized into specific test cases. This method is commonly used where exhaustive testing using every possible input is unreasonable because of the amount of inputs. By using the internal structure of the program and the outputs of the program, more efficient test cases can be created.

A basic guide as to how to implement gray box testing includes 4 steps detailed by the author. The first step of of the implementation is acquiring system knowledge. This includes documenting the internals available for use in testing, as well as the available documentation of outputs from the tested program. The second step is to define test cases according to known information and specifications. The third step is the use of both functional and non functional testing. The fourth step is to analyze the results of testing.

From the blog CS@Worcester – My first blog by Michael and used with permission of the author. All other rights reserved by the author.

Overview of combinatorial testing

In complex programs, choosing valid test cases often means choosing from many different possible combinations of inputs to find the correct cases. Doing this could take an unnecessarily long time, slowing down the development process, and possibly causing some important cases can be missed. In the article, “Combinatorial testing: What is it, how to perform, and tools”, author Shanika Wickramasinghe explains the details of combinatorial testing, and how it solves the aforementioned problem.

Combinatorial testing involves using multiple combinations of variables for testing, with the goal of this being to verify which/how many inputs of test data, with this information then being used to design future tests. Some of the benefits of combinatorial testing include a larger coverage of possible test cases than normal testing, since multiple inputs are used, reduces the cost and effort of the testing process through the fact that it’s a faster method of testing, and avoiding redundancy, as combinatorial testing ensures that the same cases aren’t tested multiple times.

To better understand how combinatorial testing works, lets take a look at the author’s example. Say we’re trying to test a username and password functionality, and we decide we’re using combinatorial testing for this. Instead of each test case only including one username OR one password, like what would happen if normal testing was being used, each test case includes 1 username and 1 password from a pool of multiple usernames and passwords. For example, let the tested usernames be User1, User2, User3 and let the tested passwords be PW1, PW2. Then, test case 1 could be testing User1 and PW1, then case 2 would test User2, PW1, then case 3 would test User3,PW1, and so on. By doing this, every case would be covered with only 6 test cases, with the same case never being covered twice.

Other types and ways of implementing combinatorial testing are available as well. For example, the example listed above would be an example of manual combinatorial testing, where cases are manually selected from the pools of parameters, where in automated combinatorial testing, software or scripts are used to automate this process and create many test cases automatically. This is especially beneficial if there are many possible inputs, and manually picking each case would be too time consuming.

From the blog CS@Worcester – My first blog by Michael and used with permission of the author. All other rights reserved by the author.

Recognizing the value of different types of unit tests

Unit testing is a very useful and secure way to stabilize the development process, but despite that, some developers forgo the testing process altogether. In Gergely Orosz’s blog entry, “The pyramid of unit testing”, he covers the relationship between the time it takes to notice the benefit of certain types of unit tests, and the experience of the coder designing the tests. For example, a coder with limited experience may look at a code body, see there are no obvious problem cases to be caught by unit testing, and forgo the process, while an experienced coder may see the same code and choose to design tests for it for reason that aren’t as obvious, and that are more based on experience and intuition.

Gergely details 5 different tiers of the Pyramid of Unit Testing Benefits, with the tiers lower on the pyramid listing benefits that can be noticed immediately, and tiers higher showing benefits that are only noticed in a later stage in development. The lowest tier are the benefits offered by tests designed to validate code changes. These tests are purely designed to validate recent changes, and make no attempt to catch future cases that might not be a problem now, showing an immediate benefit compared to not testing. The next tier up is the benefit of separating concerns in your code, which serves to somewhat force the coder to write clean code, since if a code is to be testable, that usually means that dependencies are declared upfront, leading the code to have an easier to follow structure. The next tier is the benefit of having an always up to date documentation. Well written tests can serve as useful documentation for code, and, unlike documentation of implementation code, these tests won’t get out of date. The second to last tier is avoiding regressions. If changes are made to a code body with no unit testing, than regression and conflicts are more likely to be introduced compared to a code body with tests. Designing tests helps avoid this by catching these regressions/conflicts before they become problems. The highest tier of the pyramid is using tests as a refactoring safety net. While testing is useful for validating small changes, it’s equally as useful for providing a stable environment for refactoring. When required to make large scale changes, a developer is more likely to be comfortable changing a code body with a large amount of coverage over many unit tests, rather than a code body with no means of catching problem cases.

From the blog My first blog by Michael and used with permission of the author. All other rights reserved by the author.

First CS-443 Blog

This is my first blog for CS-443. It will include posts related to software testing.

From the blog CS@Worcester – My first blog by Michael and used with permission of the author. All other rights reserved by the author.

Open source planning how it can be leveraged

https://increment.com/planning/open-source-planning/

From the blog CS@Worcester – My first blog by Michael and used with permission of the author. All other rights reserved by the author.