I want to start several discussions about some design/architecture issues we have in AX, and I think it is pretty important for each developer or tester who is responsible for the final quality of AX to read this article. After reading this you can say that it is all evident for you - but why you don’t using it then? ;)
The problem that we are using several development or design practices (or you can name it AX development habits) which are wildly used and considered normal in AX but in the "outside world" of enterprise development are well known bad practices, and have a number of different "correct" solutions, which described in a lot of literature, and successfully used by many companies.
So as I said I have several topics to discuss, but today I want to start with only one:
When do we need to use static methods in AX?
My answer to this question is: Never, or almost never
Now I can try to explain my point of view, I'm going to show several "common" examples and will show why they are bad:
Example 1:
Let consider common situation where we have 2 classes, class Foo and class Bar. They have following code:
class Bar { public static int GiveMeIntValue() { //some query which goes to AOS and retrieve some data } } class Foo { public void DoBusinesLogic() { //do something Value = Bar::GiveMeIntValue(); //do something with value } }
Before reading text below just try find at least 2 severe issues with the code.
Writing such code you are introducing a certain amount of coupling[1] into a system - coupling which is almost unnecessary. You are saying that your class can only collaborate with one particular implementation of a method. You will allow no substitutes. This makes it difficult to test your class in isolation. The very nature of test isolation assumes the ability to substitute alternative implementations.
And below I just want to summarize it in the context of our example:
- You can't write unit test for the method DoBusinesLogic, because if you will try to do it you will end up with integration[2] test not with unit test.
- If you will write test for the both methods GivemeIntValue and DoBusinesLogic - method you will get redundancy, because if GiveMeIntValue method start to work incorrectly (due to application changes), both test will fail, even if nothing wrong with DoBusinesLogic. Do you really want to spend your time with debugger to analyze a lot of tests if you can just look on the error message of one test?
- You will enforce ISV, partners or/and other teams to write bad code, and complicate upgrade to the future versions of AX. Because instead of using inheritance/aggregation and instance substitution to customize your code they will have to overwrite your code in upper layers.
Note: This example is also valid for the static table's methods, but data access is a theme for another discussion. Here I just want to say never, never, never create static table methods.
Example 2:
Several times I've heard: "We need to use static method to make server call...".
This statement is partially correct. In a lot of AX classes I saw following pattern: there was a class Foo which have instance state, and during optimization somebody decided to create one or several "server static" methods on it, to reduce chattiness. This is in most cases a design issue - single responsibility principle[3] violation. Before creating such static methods you need to stop and think - is it right place to put a method or do I need to create a separate class for it.
I don't want do discuss technics of development client server application in details here (it is a topic for one of the next discussions). But here I want to mention that you can use static methods to create stateless service - the class which:
- Have only server methods;
- Doesn’t contain any business logic and just redirects method call to a separate class.
Example 3:
Factory methods. I think that it is nothing wrong with factory methods, but there is another related problem - instantiation of an external dependencies in the business logic methods. Let’s again consider class Foo and Bar from example 1, if we for example change GiveMeIntValue method to make it instance and then will change Foo class:
class Foo { public void DoBusinesLogic() { Bar bar = Bar::create(); //do something Value = bar.GiveMeIntValue(); // now it is instance call //do something with value } }In this case you will still have all disadvantages from Example 1.
Some technics to avoid static methods
In an ideal world, an object should be able to interact only with other objects which were directly passed into it (through a constructor, or method call). Usually it can be solved by using inversion of control[5] principle and dependency injection, but AX is different so below you can find several ways to solve problems described above:
- To solve the issue described in the example 1, you need to change both classes
You need to change all method in the Bar class to make them instance.
In the Foo class you need to define an instance variable of the type Bar, and create a way to substitute it, it can be another factory method with parameter:
class Foo { private Bar bar; public static Foo create() { return createWithBar(new Bar()); } public static Foo createWithBar(Bar _bar) { Foo foo = new Foo(); foo.bar = _bar; return foo; } public void DoBusinesLogic() { //do something Value = bar.GiveMeIntValue(); // now it is instance call //do something with value } }
Or just parm method:
class Foo { private Bar bar; public static Foo create() { Foo foo = new Foo(); foo.parmBar(new Bar()); return foo; } public Bar parmBar(Bar _bar = bar) { bar = _bar; return bar; } public void DoBusinesLogic() { //do something Value = bar.GiveMeIntValue(); //do something with value } }
Or it can be protected method which just creates and returns an instance of the Bar class, so you can override the method for unit test development purposes:
class Foo { protected Bar createBar() { return new Bar(); } public void DoBusinesLogic() { Bar bar = createBar(); //do something Value = bar.GiveMeIntValue(); //do something with value } }
- For the example 2 you have to separate responsibility and create to different classes , both are instance but one of them marked as server.
- Example 3 can be solved by using external factory class, or again by using a new protected method which will create an instance of required class.
Wrong way:
class Foo { public void DoBusinesLogic() { Bar bar = Bar::create(); // Bad code //do something Value = bar.GiveMeIntValue(); //do something with value } }
Correct way:
class Foo { public Bar createBar() { return new Bar(); } public void DoBusinesLogic() { Bar bar = createBar(); // Good code //do something Value = bar.GiveMeIntValue(); //do something with value } }
With external factory it is a bit difficult to create an example because it is very difficult to create such factory in a nice not "hacky" way within AX.
The good solution for this problem is using IoC container - which is actually doesn’t exists in AX :)
Conclusion
You can say that all I described here is a pretty academic, and don't fit into real world. But it is not true there are a lot of successful examples of following these principles. Also you can say that it is so rapid and convenient to just define static method and use it, but again think about testability, extensibility and maintainability of your code.
I agree that it is not a silver bullet and there some rare exceptional cases where you can't follow this rules, also we have a lot of legacy code - but I think our goal is to go in the right direction, and write high quality code which follows all modern standards and practices.
P.S. I didn't say something new in this article. I just want you to start thinking about it. And I want you to share your opinion, so please send response if you are not agree or agree with me. :)
References:
1. http://msdn.microsoft.com/en-us/magazine/cc947917.aspx2. http://en.wikipedia.org/wiki/Unit_testing
3. http://en.wikipedia.org/wiki/Single_responsibility_principle
4. http://en.wikipedia.org/wiki/Bridge_pattern
5. http://en.wikipedia.org/wiki/Inversion_of_control
0 comments:
Post a Comment