How you can implement Clean Code principles in Power Platform
- 8 minutes read - 1673 wordsThis is my first blog post of this year after some well-deserved rest. The holidays were mostly spent with family - eating a lot of gingerbread and lussebullar among other things - but also, I managed to sit down and reread the book Clean Code by Robert C. Martin. This is a good and well-known book among programmers. I wanted to list how the Clean Code principles can be implemented in the Power Platform space and make your environments cleaner and more manageable.
Concepts from Clean Code
In my opinion, Power Platform environments do tend to get cluttered with functionality and it gets hard to keep track of all the code and low-code features and functionalities added to the system. However, a lot of the principles in Clean Code can also be implemented in Power Platform.
Here are some of the main concepts from the book that I found applicable to the Power Platform:
1: Meaningful Names
Names of variables, functions, and classes should reveal their reason for existing. Meaning you should not need comments to explain yourself, the chosen name should do the explaining. More specifically class names should have nouns or noun phrase names e.g. Account, Contact, and Lead. A class name should not be a verb. Method names should be verbs or verb phrases e.g. sendEmail, retrieveAccount, and save.
2: Functions
Functions should be small and only do one thing. Also, all the code in the function should be at the same abstraction level. Switch statements and if/else statements should be avoided if possible. If not possible, they should be hidden deep inside the low-level objects. E.g inside a factory class. Function argument should be kept as few as possible but can be good to use to highlight dependencies to other functions.
3: Objects and Data Structures
This one can be a bit tricky to grasp, but Clean Code states we should not create hybrids between objects and data structures. An object hides its data in private fields while exposing methods and functionality. On the other hand, a data structure has no functionality only data that it exposes.
The trade-off between objects and data structures is that objects leverage OOP and lets you easily add new derivative types without affecting any of the existing functions. However, if you want to add new functions you would have to implement this in all the derivative classes. Writing procedural code, using data structures, on the other hand, lets you add new functions without affecting its subtypes. But adding new subtypes would mean all of the existing functions would have to be rewritten to accommodate for this new type.
There are occurrences of hybrids of these two concepts, but they should be avoided as they combine the worst of the two concepts.
4: Unit Tests
I have heard of a lot of seasoned developers stating that integration tests are good, but unit tests are unnecessary. For the longest of times I didn’t have an opinion about this, but last year I came around. Projects with plenty of business logic became harder and harder to maintain. Every time I had a new requirement there was a sense of anxiousness as to whether some of the existing functionality still works as intended after my changes. This forced me to re-evaluate how I was working and eventually led me to unit tests. Having unit tests gives you confidence in your logic, you know that the logic works given certain conditions and when you make changes to the logic you can still verify that existing and new logic works as intended.
The general way to write tests is to use what Clean Code calls Build-Operate-Check. As far as I can tell this is the same concept as Arrange, Act, and Assert. You set up some test data in the Arrange phase, you execute your logic with the test data in the Act phase, and lastly, in Assert verify that the output is what you expect it to be.
How to implement Clean Code in your environment
Generally, I find it cleaner if an environment uses either code solutions or low-code solutions. Mixing e.g. plugins with flows is a bit ugly and forces you to look in two places whenever you need to read some event logic. With that in mind, sometimes it is just unavoidable. There might be several persons with different competencies working in the environment and they will have their preferences regarding code or low-code. Still, the system gets easier to maintain if we just pick one and stick to it.
1: How to use meaningful names
Meaningful names are equally important in the power platform low code space as in code. How to set meaningful names in code is quite well documented in the book Clean Code so I won’t go into this too much. However, for flows, there should be some sort of naming convention implemented. Stefan has a good post about this. Also, there needs to be a clear description of the flow and its steps.
2: Use Functions
Functions should be small and only do one thing. Again, this is documented for code but not as much when it comes to flows. We want the flows to be short and snappy. Each flow should have a clear purpose and only do one thing, given the abstraction level. So ideally most flows should be child workflows that then can be used and reused by parent flows.
3: Use of Objects or Data Structures should be a conscious design decision
In projects, I often end up at crossroads where the same logic can be done either in a plugin or a flow. Which one I choose often depends on the specific scenario but when the choice is arbitrary, I usually pick the solution that is already implemented. Meaning if there are already existing flows implemented, I stick with flows, if there are plugins I stick with plugins.
Based on the discussion about objects and data structures there is one more aspect to consider when picking flow or plugin. Do we want to leverage the object-oriented benefits (meaning ease of adding new types without changing existing functions), or do we want to leverage the procedural benefits (the ease of adding new functions without changing existing data structures)? In CRM this boils down to whether to use flows (combined with child flows) or plugins. As Uncle Bob says in Clean Code, we will sometimes want the flexibility to add new behaviors. So we use procedural solutions. Other times we want the flexibility to add new data types, so we prefer object-oriented solutions. This aspect should be considered when making choices between plugins and flows. Do I want to leverage object-oriented or procedural benefits?
Here is an example of the difference between procedural and object-oriented code.
Procedural code
public class Opportunity
{
public int accounttype { get; set; }
}
public class Calculate
{
public decimal CalculateTotalRevenue(Opportunity opportunity)
{
if (opportunity.accounttype == (int)accounttype.Distibutor)
{
// custom logic
} else if (opportunity.accounttype == (int)accounttype.Reseller)
{
// custom logic
}
else if (opportunity.accounttype == (int)accounttype.DirectCustomer)
{
// custom logic
}
}
}
public enum accounttype
{
Distibutor = 1,
Reseller = 2,
DirectCustomer = 3,
}
Object-oriented code
public abstract class Opportunity
{
public abstract void CalculateTotalRevenue();
}
public class DistibutorOpportunity : Opportunity
{
private decimal totalRevenue { get; set; }
public override void CalculateTotalRevenue()
{
//custom logic
totalRevenue = 10;
}
}
public class ResellerOpportunity : Opportunity
{
private decimal totalRevenue { get; set; }
public override void CalculateTotalRevenue()
{
//custom logic
totalRevenue = 20;
}
}
public class DirectCustomerOpportunity : Opportunity
{
private decimal totalRevenue { get; set; }
public override void CalculateTotalRevenue()
{
//custom logic
totalRevenue = 5;
}
}
As you can see in the picture below, the procedural code can easily be implemented in a flow. Meanwhile, since flows do not support polymorphism, replicating object-oriented code will be harder.
4: Implement Unit Tests
Plugin
Unit tests are an absolute necessity for plugins. There is a good and well-known framework written by Jordi Montana called FakeXrmEasy that alleviates the process of testing plugin code. I have set up a Github repository showcasing how unit tests for plugins might look like.
Essentially unit tests for plugins help you in ensuring changes don’t break existing functionality. Furthermore, you avoid having to go through the tedious process of updating the assemblies each time you want to verify if your code changes function as intended. Also, you get to avoid the plugin profiler for debugging. That is always a time saver and alone motivates the use of unit tests.
Power Automate and Workflows
Testing power automate flows and workflows could be a bit trickier. First, I guess you really can’t unit test power automate flows per se, since technically flows run at a higher abstraction level than units. This also makes them harder to test, since ideally, you would like to be able to test each step in the flow and not just the eventual output of a flow. Still, Power automate does have capabilities to run on previous data, but it is not like you can press a button and run a couple of tests. In other words, the tests are not particularly easy to set up and run. Maybe there is a good tool for this, or maybe somebody reading this would like to build one but to the best of my knowledge, there is no obvious tool for testing each step along the flow.
Conclusion
Clean Code principles to me are not only about code, but they are also about creating maintainable solutions, no matter at what abstraction level the logic is written in. The important thing is that we achieve structure and order. Implementing good habits to avoid chaos is a good thing. So, no matter whether we use code or low code, clean code principles can be enforced. These principles will spare us major headaches next time a change request is made as well as save our customers time and money down the line.