📜 ⬆️ ⬇️

Double-edged stick or again about the fragility of the code

I am writing this topic as a response to a recent article “10 tricks that destroy the fragile beauty of the code,” in which a lot of controversy flared up.

Much has already been written in the comments, much is not written. I just want to show here real examples, that there are many situations where the Author is wrong, where the proposed solutions will interfere.

The author is largely right, but he is too categorical in his convictions. The profession of a programmer is to think and describe the solution of the problem in an optimal way. As in any art, if the programmer solves all the tasks “substituting only templates”, then all his decisions will be utter nonsense. A programmer is a person who can think, and programming languages ​​provide a fuckingly flexible interface for implementing decision algorithms. For this, you should not give up the features of the language - because it is written not to use the template - the language developers have already thought about everything 100 times for you to leave these methods and I am sure that you can find the most suitable methods in the documentation.
')
What the author has told is good advice. But he showed only one side of the coin. I'll show you another one now.

Declaration of all variables at the beginning of the program


I consider the following statement to be absurd:
Declaring all variables at the beginning of a function is a terrible evil.

Yes, Steve writes correctly - ideally, immediately declare and define each variable immediately before first accessing it . But this is not true - this is only an ideal case. And the cases are different, including not quite perfect.
In this case, it is better to define some variables at the beginning of the code - for better understanding of what is happening.

In the following example, it is clearly seen that the function returns array ():
  1. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  2. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  3. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  4. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  5. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  6. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  7. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  8. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  9. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  10. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  11. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  12. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  13. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  14. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  15. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
  16. function sql2array ( $query , $field = false ) { $Result = array ( ) ; if ( DEBUG ) print ( "Query: $query \n " ) ; $queryResource = $this -> query ( $query ) ; if ( ! $queryResource ) return false ; while ( $Data = $this -> fetch_array ( $queryResource ) ) { if ( ! $field ) $Result [ ] = $Data ; else $Result [ $Data [ $field ] ] = $Data ; } $this -> free_result ( $queryResource ) ; return $Result ; }
If I set $ Result after checking! $ QueryResource, then I’d have to quickly re-read the code in search of the type of this variable.
That is why in C all variables are defined at the beginning. You read them as a table of contents (books) of the code, already understanding what will happen in the code. But this is not a panacea either, but only the other side of the force. :)

Variables that are used only in calculations, cycles, but, for example, are not returned, you can (and in the ideal case, even need to) declare before calculations. (as done, for example, with $ queryResource).
Just imagine: We have a function in 300 lines of code [2]. Somewhere on the 200th line, we need to swap two variable places. To do this, we climb 200 runoff higher to the beginning of the function, declare a variable temp, which has nothing to do with the whole function, but is used only once in one place, then again we return to the 200th line and change variable places ... I think , it's just a nightmare.
It's not scary. When you write code, you know what's going on in it. It becomes scary to other people when they read the code and tries to find the variables they need in 300 lines.

Therefore, a good programmer will first of all get rid of unnecessary code by doing the simplest Refactoring of each method (especially when they take 300 lines), breaking it down into lighter methods and functions with clear, obvious names.
In this case, everything will turn out to be simpler and without unnecessary problems “where the variables must be declared”. In the ideal case, they will be announced where they are visible - most likely at the beginning, and used in the block almost immediately (in atomic methods).

So, just think what and how you write the methods / functions. And do more refactoring. :)

Returning the result of a function through its parameter


Again there are understatements. The author correctly says that when there is no need, it is worth using a return. But the need sometimes arises!

An implementation of a function that returns several values ​​of different types:
  1. function sql2array ( $ query , $ field = false , & $ error = NO_ERROR ) {
  2. $ Result = array ( ) ;
  3. if ( DEBUG )
  4. print ( "Query: $ query \ n " ) ;
  5. $ queryResource = this -> query ( $ query ) ;
  6. if ( ! $ queryResource ) {
  7. $ error = QUERY_ERROR ;
  8. return false ;
  9. }
  10. while ( $ Data = $ this -> fetch_array ( $ queryResource ) ) {
  11. if ( ! $ field )
  12. $ Result [ ] = $ Data ;
  13. elseif ( isset ( $ Data [ $ field ] ) )
  14. $ Result [ $ Data [ $ field ] ] = $ Data ;
  15. else {
  16. $ error = FIELD_ERROR ;
  17. return false ;
  18. }
  19. }
  20. $ this -> free_result ( $ queryResource ) ;
  21. return $ Result ;
  22. }
As you can see, the function now returns two parameters - this is $ error - the status of the function execution and an array, if correctly executed.

But to do so in such cases is also not a panacea. Since this is a class method, then it would be most appropriate to add the SetError (errorType) and errorType GetLastError () methods to this class. And use them in all methods. Practical, convenient and easy to implement:
  1. class A {
  2. private $ error ;
  3. public function getLastError ( ) {
  4. return $ this -> error ;
  5. }
  6. private function setError ( $ error ) {
  7. $ this -> error = $ error ;
  8. }
  9. public function sql2array ( $ query , $ field = false ) {
  10. $ Result = array ( ) ;
  11. if ( DEBUG )
  12. print ( "Query: $ query \ n " ) ;
  13. $ queryResource = this -> query ( $ query ) ;
  14. if ( ! $ queryResource ) {
  15. $ this -> setError ( QUERY_ERROR ) ;
  16. return false ;
  17. }
  18. while ( $ Data = $ this -> fetch_array ( $ queryResource ) ) {
  19. if ( ! $ field )
  20. $ Result [ ] = $ Data ;
  21. elseif ( isset ( $ Data [ $ field ] ) )
  22. $ Result [ $ Data [ $ field ] ] = $ Data ;
  23. else {
  24. $ this -> setError ( QUERY_ERROR ) ;
  25. return false ;
  26. }
  27. }
  28. $ this -> free_result ( $ queryResource ) ;
  29. return $ Result ;
  30. }
  31. }
Voila
True, now this code cannot be used in multi-threaded programs (the last was possible), but it all depends on the task set. Moreover, the commentators correct me that here you can use for error handling - exceptions, where I fully agree with them.

Here we smoothly proceed to examples where you need to use access to the properties of an object through object.getProperty () and object.setProperty (value) .

Access to object properties


Let us consider the case when it is obviously necessary to refine the method with a check for value:
  1. class A {
  2. protected $ length ;
  3. public function getLength ( ) {
  4. return $ this -> length ;
  5. }
  6. }
  7. class B extends A {
  8. public function getLength ( ) {
  9. if ( $ this -> testLength ( ) )
  10. return ( $ this -> length + 1 ) * 100 ;
  11. return parent :: getLength ( ) ;
  12. }
  13. }
Fully classes, I do not cite. But it already becomes clear for what reasons it is worth thinking about which method to use to change a variable. With all that, it is worth thinking about how your class can use in multi-threaded applications, or for example, when working with a database.

It all depends directly on the architecture (and programming language). You can (and should) use properties where they are, and methods where there are no properties. :)
In some cases, it is justified to use public (without unnecessary overloads, when it is necessary to simply store / read the values ​​of the structures), in others, operator overloading, in the third - impact methods, in the fourth - properties.

Absence of named function parameters


Here the camp is divided into several groups. OOP lovers and functional programming lovers.

I will only show that named parameters do not work everywhere.
In languages ​​without their support, problems immediately arise:
1) The need for manual verification of all incoming parameters.
2) The need for knowledge of the names and quantities of these parameters. (when calling)
And another one.
Imagine that we have several functions. DrawRectangle, DrawCircle, DrawConus, DrawLine, DrawPoly ... The list goes on and on.

The author proposes to declare and use (for example C) for each object its own structure. That is, it turns out that for each function, its own structure is necessary - you will agree nonsense. At the same time, the structure contains coordinates, color, transparency, etc.

In procedural programming in a good way, they are divided into “atomic” pre-initialization functions. For example - this is implemented in OpenGL. But I will give an example of the author:
  1. BackgroundColor ( 1 , 1 , 255 ) ;
  2. BorderColor ( 255 , 1 , 1 ) ;
  3. BorderWidth ( 2 ) ;
  4. DrawRotation ( 30 ) ;
  5. AlphaColor ( 20 ) ;
  6. DrawRectangle ( 80 , 25 , 50 , 75 ) ;

The code is obvious and clear. In this case, the parameters can be arbitrarily many without additional tweaks. And the functions take the parameters that they should take.

And now about the minuses of the author's approach. Why you should not do as he says (in languages ​​without the support of named parameters):
  1. void DrawRectangle ( Rectangle rect ) {
  2. Line line ;
  3. float x1 , y1 ;
  4. float x2 , y2 ;
  5. x1 = rect. x - rect. width / 2 ;
  6. y1 = rect. y - rect. height / 2 ;
  7. x2 = rect. x + rect. width / 2 ;
  8. y2 = rect. y + rect. height / 2 ;
  9. line. color = rect. borderColor ;
  10. line. weigth = rect. borderWeigth ;
  11. line. alpha = rect. alpha ;
  12. // rotation and so on, let's imagine, forgot ...
  13. line. x1 = x1 ;
  14. line. y1 = y1 ;
  15. line. x2 = x2 ;
  16. line. y2 = y1 ;
  17. DrawLine ( line ) ;
  18. line. x1 = x2 ;
  19. line. y1 = y1 ;
  20. line. x2 = x2 ;
  21. line. y2 = y2 ;
  22. DrawLine ( line ) ;
  23. line. x1 = x2 ;
  24. line. y1 = y2 ;
  25. line. x2 = x1 ;
  26. line. y2 = y2 ;
  27. DrawLine ( line ) ;
  28. line. x1 = x1 ;
  29. line. y1 = y2 ;
  30. line. x2 = x1 ;
  31. line. y2 = y1 ;
  32. DrawLine ( line ) ;
  33. }
Practically, the same thing happens if you use the Command pattern.
Thus, not all methods are good. And what the author described is not a panacea at all.

It is not always useful to transfer functions to all parameters in ONE.
Using procedural programming, you can simply transfer the finished DrawLine results (x1, y1, x2, y2). It will look like this . (without additional initializations of Color, Alpha, LineWeight, etc.)

About data duplication


(see the first comment from za4to ) I will only add that it already describes the stage of system optimization.
When optimizing, optimization rules are already working in the first place, where all means are good :) (sometimes even Denormalization). At the same time, even while optimizing, it is necessary to ensure that the code looks readable.

Instead of conclusion


The art of a programmer is a complex art in which not only problems are solved, but also certain skills are honed. These skills can be useful, but do not get carried away with them too much. In some cases, other methods of implementation will be more suitable, high-quality and more visual for solving your problem.

Use both sides of the force. :)
Successes. And I expect additional comments from you.

Source: https://habr.com/ru/post/59642/


All Articles