One of the main aspects in the development of software in general and web applications in particular, I consider the ability of software to be changed - adaptable to changes in the surrounding world. This does not mean that the developer should foresee future changes in the habitat of his code, this means that the code must undergo many refactoring cycles, while remaining operable as long as possible. And for this, it is necessary that the consequences of changes made to the code are either foreseeable or predictable. Under the cat, I summarized my understanding of the areas of code hiding, formed as a result of close, almost intimate, relations with Magento 2 (platform for building online stores). The following applies firstly to the PHP language, secondly to web applications, and thirdly to everything else.
The simplest case is when our changes do not go beyond the local scope. By local scope, I mean the function / method body:
function foo(int $in): int { $out = $in * 2; return $out; }
The code inside the function can be changed as you like, since the effect of changes is observable (limited to the body of the function / method). However, we cannot safely change the name of a function — in general, we have no idea who uses our foo () function, and what code will stop executing if we rename the function to boo () . That is, changes in the function body are local (foreseeable), and changes in the function name (signature — taking into account input and output parameters) are global (unpredictable).
In the context of this publication, I view the changes in the code, and not in the functionality implemented by the code. Obviously, if the foo () function should add an entry to the database, and after refactoring it removes the entry from the database, such a drastic change in behavior will lead to unpredictable consequences for all external code fragments using the foo () function. However, from the point of view of the call (conjugation of the foo () function with external code), it remained unchanged.
Encapsulation (or rather, concealment) makes it possible to move part of the functions from the global area of ​​influence of changes to a less global one - at the class level:
class Foo { private function localChanges() {} public function globalChanges() {} }
The part of the code that is in private functions (and the private functions themselves) can be reworked quite painlessly for the whole world around. The scope of change is limited to the class body.
Unfortunately, it is impossible to say about the protected-functions of the same thing - the area of ​​influence of changes for them is no different from public functions. It is also global .
The rules of good form (and human abilities) do not recommend us to create "sheets" of code of several thousand lines of code within a single class. Suppose that we have some functionality, for the implementation of which we objectively need to write several thousand lines of code, but it will be called from one point (the public method of a certain class). Obviously, the scope of the change will be global for only one method, and the rest of the functionality can be divided into private methods:
class MegaFoo { private function validateInput($in) {} ... private function prepareOutput($in) {} public function exec($in) {} }
The principle of decomposition forces us to break the "sheet" of several thousand lines of code into its component parts (classes), hiding their internal implementation in private methods, and combine them with each other using public methods:
namespace Vendor\Module\MegaFoo; class Boo { public function validateInput($in) { $result = ($in > 0) ? $in : 0; return $result; } }
namespace Vendor\Module\MegaFoo; class Goo { public function prepareOutput($in) { $result = number_format($in, 2); return $result; } }
namespace Vendor\Module; class MegaFoo { private $boo; private $goo; public function __construct( \Vendor\Module\MegaFoo\Boo $boo, \Vendor\Module\MegaFoo\Goo $goo ) { $this->boo = $boo; $this->goo = $goo; } public function exec($in) { $data = $this->boo->processInput($in); $result = $this->goo->prepareOutput($data); return $result; } }
The scope of change for private methods of the classes created will be limited to the bodies of the classes themselves. But the scope of the changes to the public methods processInput ($ in) and prepareOutput ($ data) for the classes \ Vendor \ Module \ MegaFoo \ Boo and \ Vendor \ Module \ MegaFoo \ Goo will be limited to the class hierarchy:
Is it possible to conclude from the code of the classes \ Vendor \ Module \ MegaFoo \ Boo and \ Vendor \ Module \ MegaFoo \ Goo that their area of ​​influence of changes is limited? Unfortunately not. Nothing forbids any third-party developer to use the \ Vendor \ Module \ MegaFoo \ Boo :: processInput method directly in their code, since there are no markers anywhere in the code that limit such an action. That is, in fact, we have a limited area of ​​influence of changes, but the lack of tools for its description does not allow us to take advantage of this. Of course, at the level of a separate project, one can discuss such options at the level of agreements operating in the development team.
To create complex applications, developers are forced to use the results of each other's work. These results are designed in the form of libraries, frameworks, modules for these frameworks. IMHO, Magento 2 is at the forefront of this kind of cooperation. In essence, this platform is a set of modules (magento modules) created on the basis of some framework (Magento 2) that uses third-party libraries (Zend, Symfony, Monolog, ...). Magento-module is quite a separate block from which applications are created and whose functionality other magento-modules can use. It is quite obvious that the code inside the module as well as in the class can be divided into 2 parts - public and private. Public is the code that is supposed to be used by other modules of the final application (I am not sure that the code called by the framework itself belongs to the public part), private is the code that the developer of the module does not intend to use outside of its module. Using the example of the evolution of Magento 2’s own modules, you can see how a set of public interfaces is formed in the ./Api/ folder in the module’s root.
If you develop this idea, then in the limit you can come to an agreement that the developer of the module explicitly, through interfaces, indicates the functionality that he intends to make public in his module, declaring that all the remaining code belongs to the closed part of the module and can be it has been reworked without regard for its use by third-party code. Thus, the impact of changes for the closed part of the module is limited to the files of the module itself, i.e. - becomes foreseeable.
The development of the idea of ​​a declaration in the explicit form of public interfaces of a module to the application level can be seen in the example of the same Magneto- Swagger API . Even if the level of influence of changes, this level is already weak, because from the point of view of web application developers, the scope of change for the entire application coincides with the global scope.
The quality of refactoring can be significantly improved if the principle of concealment (separation of code into private and public parts) is applied not only at the class level, but also at the level of class groups connected by the implementation of a single functionality, and at the level of the modules from which the application is built.
Please do not throw tomatoes flood in the comments, if what is stated in the article is not applicable or poorly applicable in your area of ​​activity - this is just a formalization of my personal experience. Do not like - minus. And yes, I know that all this has already been invented before me. Thanks to those who read it.
Source: https://habr.com/ru/post/269425/
All Articles