As developers we have always had two possibilities on how to write our code, the first has come to be known as the cowboy method 🤠 because it works like we are living in the wild west, where everything goes and the most important is the result and the speed and not necessarily the stability and maintainability of the solution. In the other camp, we have the developers that only follow best practices and make sure that the solution lives up to all factors like readability, testability, maintainability, and so on 🤓. It is essential to state that one is not necessarily better than the other, and most developers find themself somewhere in the middle 🧐. However in the NAV / BC community, we are seeing that the cowboy method is more used than in many other developer communities, and I believe that the reason for this is that many of the developers that we have in our community are self-taught business consultants that turn developers 🤔. This has not been that much of a problem in the past, but it is becoming one now, the reason is that we are in the need of more BC developers, but the way that we get more BC developers is by finding some people (hopefully with a developer background) and then placing them next to a mentor who can guide them, which is a great way to get new BC developers, however, the dangerous thing about this method is that if these mentors are not only teaching them how to code in BC but also teaching them all their bad habits 😉, on top of that it can be difficult to come as a new developer full of energy and new ideas only to be told by a senior developer that everything that you are doing is wrong and that it is a waste of time, trust me I have been there, or the good old saying “Yes that is the right way to do it, but the customer does not want to pay for it so instead do it like this”. But no more 😵, we need to be better, it might be difficult to adopt a new way of working, but in the long run, it will give us a better product, happier customers, and happier developers, but every journey starts with one step and in this context, the first step is to change our own behavior so that we can pass on our good behavior to our new colleagues, and just one final thing before we get started; please listen to the young developers, they might not have the same experience as the senior developers, but they are in a place where you only find yourself once in your carrier, which is the stage of innocence and with the belief that everyone always does everything the correct way and not just the fastest way, please encourage them to keep this mindset 🙂.
On to the post itself 😉, first, let us start off by looking at the challenge; in BC cloud anyone can write an extension and deploy it for free, this includes the customers themselves, or anyone that the customers allow, which is great because it expands the availability of BC thereby hopefully helping in getting new people interested in the platform, on the downside this also raises some challengers for us developers, because what this means is that anyone can extend and change the behavior of our extensions, and in the worst case they can break them 😟, and while most people might think that, that is not a problem with their customer I challenge you to think in the lanes of what if. So the idea is to write all your code with the simple mindset that if something can be extended it will be extended and therefore we need to handle this problem in some way.
Method 1:
We can close all our code down, by setting everything to private and internal, thereby solving the problem because you cannot break what you can not change 😉, however, this is not the best solution, because by doing this you are also shutting everyone out making it very difficult for other people to help maintain it.
Method 2:
Write your code so that it can only be extended and accessed in the way that you know works, and yes you guessed it this is the method that I am going to focus on here 😉
Let us start with Enums, this is one of the biggest ones that a lot of people fall into if they of cause use enums which you should 🤓. With the introduction of enums, we were all given the ability to extend options like in the old NAV days, however in the old NAV days, it was the person who extended the option field who was responsible for that did not break anything, that is no longer the case in BC if we create an Enum and set it as extensible it is our responsibility to ensure that if other people can extend it both without breaking our code but also in a meaning full way 🤓, let us look at the following example.
enum 50100 Colors
{
Extensible = true;
value(0; Blue)
{
Caption = 'Blue';
}
value(1; Red)
{
Caption = 'Red';
}
}
This looks pretty harmless, let us now create some code that uses this Enum:
codeunit 50100 "Use Enum"
{
procedure DoSomethingWithEnum(Colors: Enum Colors)
begin
case Colors of
Colors::Blue:
begin
Message('Blue');
end;
Colors::Red:
begin
Message('Red');
end;
end;
end;
}
So in this simple example, everything looks fine, but since we made our Enum extensible, what will happen if someone adds a new color 🤨, well adding a new color in itself is not a problem however, if that person wanted not only to add a new color but they also want something to happen if someone uses our new color, that is not possible in the code above, so what do we need to do? Well, we are going to need to add an Event so other developers can expand our case statement.
codeunit 50100 "Use Enum"
{
procedure DoSomethingWithEnum(Colors: Enum Colors)
begin
case Colors of
Colors::Blue:
begin
Message('Blue');
end;
Colors::Red:
begin
Message('Red');
end;
else
OnCustomColor(Colors);
end;
end;
[IntegrationEvent(false, false)]
local procedure OnCustomColor(Colors: Enum Colors)
begin
end;
}
Now our code is already much more extendable, however, we can still do better, because what if someone uses our new event and does something that we did not intend to happen? 🤨To stop this we need to make a contract on how people are allowed to extend our code and to do this we will need to create an interface.
interface IColor
{
procedure GetColor(): Text;
procedure UpdateItemDescription(var Item: Record Item);
}
This Interface will force anyone who wants to extend our Enum to create a new codeunit with the two functions GetColor and UpdateItemDescription, so your codeunits could look like this:
codeunit 50101 Blue implements IColor
{
procedure GetColor(): Text
begin
exit('Blue');
end;
procedure UpdateItemDescription(var Item: Record Item)
begin
Item.Description := GetColor();
end;
}
codeunit 50102 Red implements IColor
{
procedure GetColor(): Text
begin
exit('Red');
end;
procedure UpdateItemDescription(var Item: Record Item)
begin
Item.Description := GetColor();
end;
}
codeunit 50103 NoColor implements IColor
{
procedure GetColor(): Text
begin
exit('You have choosen no color');
end;
procedure UpdateItemDescription(var Item: Record Item)
begin
error(GetColor());
end;
}
And let us implement the Interface to our Enum
enum 50100 Colors implements IColor
{
Extensible = true;
DefaultImplementation = IColor = Blue;
UnknownValueImplementation = IColor = NoColor;
value(0; Blue)
{
Caption = 'Blue';
Implementation = Icolor = Blue;
}
value(1; Red)
{
Caption = 'Red';
Implementation = Icolor = Red;
}
}
Iif you get into the habit of implementing this pattern when creating extensible Enums, you are on your way to writing better code 🙂, next let us look at making your code extendable, the first thing that you need to think about is if your functions should be local or global, because if you make your functions global there is a good chance that someone will use your function which might be all right but in some cases, you might not want people to call your function outside the context that you had in mind when you wrote it, a good rule of thumb is to keep all your functions local unless there is a need for it to be global, then it is better to create a “safe” way for someone to call your functions; another thing that we need to stop doing is creating hugh god codeunits that can do a lot of things, this way of structeringer our code is an old legacy way of thinking from back in the days of our onpremise solutions where we had to pay for each object that we used,💸 which meant that we had to disucse with the end customer if we should buy new objects to their license and focus on the good design or if we should keep it as cheap as possible and compromise our design; 🤯 but this is not relevant anymore because in the cloud we no longer have to pay for objects, which means there is no reason to create these god codeunits anymore, so it is time to change our mindset and create a codeunit per flow. Let us look at a simple example.
codeunit 50103 Car
{
procedure OpenDoor()
begin
end;
procedure TurnOnEngine()
begin
end;
procedure PressSpeeder()
begin
end;
}
As you can see the above codeunit has three global functions, which might be fine as long as the person that calls the codeunit knows how to use these functions, 🧐 but what if someone calls TurnOnEngine before they have called OpenDoor that might cause some problems in our flow, there are two ways of avoiding this the first way is to set up some preconditions on each function, to make sure that everything is only being called when it should, but an easier way is to make them call local and then create a new function that will call all the other functions in the correct order like so.
codeunit 50103 Car
{
procedure DriveCar()
begin
OpenDoor();
TurnOnEngine();
PressSpeeder();
end;
local procedure OpenDoor()
begin
end;
local procedure TurnOnEngine()
begin
end;
local procedure PressSpeeder()
begin
end;
}
This way of structuring our code makes it so that our code can only be called in the way that we intended, and it also has the added bonus that it is much easier to read because anyone that looks at our code can easily see in what order our code is being executed, 😎 but what if someone would like to change the behavior of your code in one of the functions, well this is where you should think about adding in events, however, do not just blindly add in events, because while something like this would be great for most developers who want to extend your code:
local procedure OpenDoor()
begin
OnBeforeOpenDoor();
//Your code
OnAfterOpenDoor();
end;
local procedure TurnOnEngine()
begin
OnBeforeTurnOnEngine();
//Your code
OnAfterTurnOnEngine();
end;
local procedure PressSpeeder()
begin
OnBeforePressSpeeder();
//Your code
OnAfterPressSpeeder();
end;
It also forces you to make sure that if someone subscribes to one of these events it should not break any of your code, and furthermore, once you add these events you also make a contract to everyone that you will not introduce any breaking changes to this code yourself, thereby in many ways cementing your code 😬. So you have to be careful to be too open with your code because remember the quality and stability of your code is your responsibility, now I know that I make it sound like everything is our responsibility, and that is of cause not true, 🙃 if someone wants to break our code they will always find a way, but the idea is to make it so that people do not break it unintentionally, a final thing that I will mention in this already very long post, is that you need to think about rather or not someone should be allowed to disable your code, this is done by introducing the IsHandled pattern, this should only be used if you think that someone might want to make such large changes to your code that they wish to implement their own version of your code, the pattern is quite simple to implement, all you do is add an event with a boolean called IsHandled that the customer can then set, and if they do so you will just exit out of your code.
local procedure OpenDoor()
var
IsHandled: Boolean
begin
OnBeforeOpenDoor(IsHandled);
if IsHandled then
exit;
//Your code
OnAfterOpenDoor();
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeOpenDoor(var IsHandled: Boolean)
begin
end;
If you choose to implement the IsHandled pattern you pretty much move the responsibility of breaking code to the people who choose to subscribe to your event, and that is it for this post stay tuned for part 2, and until next stay safe. 😃