What is beautiful code, and how to write it? Code formatting. Functions must do one thing


I will give the basic rules that must be followed when developing code, so that in a week you can look at the code and determine which function to do what. Another advantage of writing readable code is the ease of finding and fixing errors. I’ll say right away that the examples that I mark as “incorrect” do not mean that they do not work, but that they are incorrect from the point of view of developing readable code.

1. Take variables out of brackets

Always when displaying text on the screen, the variables in the line must be placed outside the brackets. This is not only convenient for viewing, but also effective, since it results in faster output to the screen.

Incorrect example:

echo "Value is $val"

Correct example:

echo "Value is ".$val

2. Be sure to use comments

Each function should be described with a short comment if possible. Each fragment, if you think it is difficult enough to understand, should be described with a short comment. And in general, the comment should describe everything that you consider necessary. You must decide for yourself what type of comment you want to use. This can be one text line, or a block of lines, which describes the purpose of the function, information about its author, etc.

3. Use a shorthand version of the echo function. For example, a record like

can be easily replaced with

4. If possible, move large blocks of HTML outside of PHP constructs. Don't abuse the php function.

Incorrect example:

"; echo "Number before is ".($i - 1); echo "
"; } ?>

Correct example:

Number is
Number before is

Please note that two php constructs are opened here, and between them is inserted HTML text. Perhaps this example does not show a clear advantage of moving text outside the PHP structure, but in fact, when you have to deal with tables, such a rule can be very useful.

5. The code must be aligned relative to the blocks.

Incorrect example:

Remember, php is not Pascal with its begin...end blocks. Here the block should open on the same line where it started, and close aligned with the beginning of the block:

6. Simplify complex designs. Break them down into simple ones.

Incorrect example:

$res = mysql_result(mysql_query("SELECT Num FROM db"), 0, 0)

Correct example:

$query = mysql_query("SELECT Num FROM db"); $res = mysql_result($query, 0, 0);

7. Use more spaces and empty lines.

In fact, this is a fairly important element of writing readable code. I've seen some people leave no empty lines and use as few spaces as possible, thinking that they take up space. extra space. This is fundamentally wrong, since the extra bytes will allow another person to save time on parsing someone else’s code.

Incorrect example:

Correct example:

8. Use shortcuts for math and string operations.

Remember that +1 can always be replaced with ++, and +n with +=n.

Examples of substitutions:

$i = $i + 1 is equivalent to $i++
$i = $i — 1 is equivalent to $i—
$i = $i + $n is equivalent to $i+=$n
$i = $i."hello" is equivalent to $i.="hello"

Good bad

Knowing how to write code does not mean being able to do it correctly. Good code- it’s like a beautifully designed post - it’s easy to read, easy to navigate, and the developer always understands how to expand it, debug it, or use it for other purposes.
Therefore, it is always important to pay attention to the design of the code and comment on complex sections of the code.

First, a little theory. The same code can be written in different ways. The code may be procedural, functional And object-oriented.

Procedural approach

The procedural approach is the simplest. It means a script in which commands are written and elementary PHP functions are called.
For example:

$a = 10;
$c = $a % $b;
$e + $d;
echo round($e);
?>

This approach is appropriate if you have a very small code or it performs strictly one task (for example, generating a picture), as they say, “neither step to the right, nor step to the left.”

Functional approach

In the functional approach, your code is divided into parts and each part is placed in its own function. This will avoid code duplication, make the code concise and easier to read.
Also, each function will have its own variable scope. That is, variables “living inside it” will never leak outside and variables used outside the function will also not end up in the function itself.
By defining a function, you yourself can specify which variables should go into the function and what it should return, that is, you strictly control this process.

Now let's try functional.

//will return the cost
function getPrice($price, $weight) (
$result_price * $weight;
return $result_price;
}
//return all weights
function getWeights() (
return array(5, 12, 14, 16, 22, 135, 150, 200, 254, 300, 400);
}

//get the weights into the variable
$weights all fruits
foreach ($fruits as $fruit) (
foreach ($weights as $weight) (
echo $fruit["name"] . " " . $weight. "kgs cost". getPrice($fruit["price"], $weight) . "rub.
";
}
}
?>

The code came out much more readable. The weights are specified in the function getWeights and by simply adding them there, calculate how much a different weight of each fruit would cost.
I went through all the fruits and with each search I went through all the weights. It could have been done the other way around.
Source on pastebin http://pastebin.com/07QTBihX

And finally the implementation on OOP.

class Fruiting (
public $fruits;
public $weights;

public function setData($fruits, $weights) (
$this->fruits = $fruits;
$this->weights = $weights;
}

private function getPrice($price, $weight) (
$result_price = $price * $weight;
return $result_price;
}

public function getResult() (
//sort through all the fruits
foreach ($this->fruits as $fruit) (
// iterate through all the weights for each fruit
foreach ($this->weights as $weight) (
echo $fruit["name"] . " " . $weight. "kgs cost". self::getPrice($fruit["price"], $weight) . "rub.
";
}
}
}
}

$fruiting = new Fruiting();
$fruiting->setData($fruits, array(5, 12, 14, 16, 22, 135, 150, 200, 254, 300, 400));
$fruiting->getResult();
?>

As you can see, the code is more voluminous. At simple calculations You can get by with a functional approach, but all really large and complex projects are written using OOP.

When writing the material, I tried to follow the advice of @ontofractal :)

P.S. When you write code, imagine that it will be maintained by a mentally unstable maniac who knows where you live.

Do program code beautiful

In general, program code does not need to look beautiful; it just needs to be easy to read. This section offers some simple recommendations for formatting program code to make it easier for you to decipher your own program code tomorrow, in a week or next year, if necessary.

Indentation in the program

Remember: you should develop rules for using indentations and strictly adhere to them. The VBA compiler ignores all spaces at the beginning of lines, so you can safely use indentation to create order. Compare the following two code snippets and decide which one is easier to understand:

If intA = 27 Then

objCbar.Visible = True

If intA = 27 Then

If txtChooseColor.Text = Beige Then

For Each objCbar In CommandBars

If objCbar.Name = My toolbar Then

If objCbar.Visible = False Then

objCbar.Visible = True

Both of these fragments produce the same result, but the indentation in the second fragment allows you to immediately tell for each End If statement which of the If statements above it is. . .Then will be his match. As a result, it is easier to track the path of program execution depending on the prevailing conditions.

From the book The Art of Deception by Mitnik Kevin

"Do it now" Not everyone who uses the tactic social engineering, is the ideal social engineer. Anyone who owns inside information company, may bring danger. The risk is even greater for those companies that store in their files and databases

From the book Internet Intelligence [Guide to Action] author Yushchuk Evgeniy Leonidovich

Software package"Intellectum.BIS" The main purpose of the product is to provide marketing experts and analysts with information processing tools for performing business research in order to provide information to management for making management decisions.

From the book Computerra Magazine No. 705 author Computerra magazine

Make it quieter Author: Vladimir Guriev In August 2007, I visited the Hungaroring, the Hungarian stage of Formula 1. All my friends - and especially Formula 1 fans - were, of course, jealous of me, and I, as an honest person, tried not to deceive their expectations and questions like "Well, how is it there?"

From the book The C# 2005 Programming Language and the .NET 2.0 Platform. by Troelsen Andrew

Agent Code If you open the generated agent file, you will find a type that is derived from System.Web.Services.Protocols.SoapHttpClientProtocol (unless you specified a different communication protocol using the /protocol option). public partial class CalculatorWebService: System .Web.Services.Protocols.SoapHttpClientProtocol (...)This

From the book TCP/IP Architecture, Protocols, Implementation (including IP version 6 and IP Security) by Faith Sydney M

2.2.4 RPC API Although not as widespread as socket, the Remote Procedure Call API (RPC) Procedure Call- RPC) for client/server connections is often used in various systems. It was originally implemented in

From the book VBA for Dummies by Steve Cummings

Take a break! The key to debugging a program lies in VBA's pause mode. Pause mode is a temporary stop of program execution at some statement in the program code. Since in this case the program is still alive", you get the opportunity to check the current values

From the book Internet for your parents author Shcherbina Alexander

Make your choice Recently, there has been a steady trend away from mail programs. Many experienced users don't even know about their existence. This is explained by the fact that the presence of widespread and quick access to the Internet compensates for the shortcomings

From the book Doubling Sales in an Online Store author Parabellum Andrey Alekseevich

Make the ordering process logical Most users are confused by the inconsistent (non-linear) payment process - it is not clear how to buy the product, or the purchase itself is difficult. If your online store, when paying for an order, has substeps such as “Creating

From the book PC without stress author Zhvalevsky Andrey Valentinovich

Make the text beautiful, or Formatting Computer users are unsentimental people. They are embarrassed by the words “make the letters beautiful” or “make the text beautiful.” Instead, they use the expression "text formatting" and sometimes when talking about

From the book Ubuntu 10. Quick Start Guide author Kolisnichenko D. N.

23.2. Software failure First of all, you need to find out and, if possible, eliminate the cause of the failure. If it's purely software glitch, then there are two reasons: incorrect configuration of the program (or system) and an error

From the book Website Monetization. Secrets big money in the Internet author Merkulov Andrey

Make sure that your article is read to the end. The solution to the problem lies in the ability to succinctly talk about the client’s problem in the first two paragraphs of the article. However, this will be completely impossible without a preliminary study of interests target audience,

From book operating system UNIX author Robachevsky Andrey M.

TLI Programming Interface When discussing the implementation of network support in BSD UNIX, a socket-based programming interface for accessing network resources was considered. This section describes the Transport Layer Interface (TLI), which provides

From the book Digital Photography from A to Z author Gazarov Artur Yurievich

Program mode is designated by the letter P. This, in general, auto mode, but unlike Auto, it allows you to make your own changes to many parameters selected by the camera: change ISO sensitivity, white balance, select mode and AF point,

From the book Learning Explosion: Nine Rules for an Effective Virtual Classroom by Murdoch Matthew

From the book Linux and everything, everything, everything... Articles and columns in LinuxFormat, 2006-2013 author Fedorchuk Alexey Viktorovich

From the author's book

Make me... good LinuxFormat, #104 (April 2008)Eternal dream Linux users– to make everything work out of the box seems to be close to fruition. What can be observed in the example of the alpha version of Kubuntu - 8.04. Installed, as before, with a half-back, both in the Desktop version and in the

To make all further descriptions more clear to you, download this PDF file or simply open this image in a new tab of your browser.

Sometimes it is interesting to pay attention to whether a website that is beautiful in appearance always uses “beautiful” html code, i.e. precise, structured, error-free. HTML code is a kind of art. It is not as developed and diverse as dynamic language, and yet can be written quite elegantly and masterfully.

Here is a list of those features that can be used to determine whether the code is written with skill or not.

1.DOCTYPE properly defined. This string not only allows you to identify your code, but also "tells" the browser how to display your page.

2. Neat section in tags
The header is set. The encoding has been declared. Style sheets are connected. Scripts are included, but not fully written.

3. Assigning an ID to your document allows you to create CSS properties, which are unique for each page. For example, you may want your

the tag looked special, for example, on home page. In your CSS stylesheet you can write: #home h2() to achieve this.

4. Clean menu. You've probably often seen lists being used as menus. This is very convenient, because... gives you the ability to control it.

5. Concluding all page content in main tag

. This item will give you the opportunity to control everything. That is, you can set the maximum and minimum page width.

6. Important things should be placed at the beginning. News and important events in the code should come first, and menus and unimportant content should be placed at the end.

7. "Inclusion" of site elements. Most site elements are repeated on every page, which means they need to be included using the include php command.

8. The code must be tabulated into sections. If each section of code is indented, the structure of the code will be much clearer. Code that is left aligned is difficult to read and understand.

9. Proper closing tags. Exclude end tags in unpaired tags, and check that matched tags are closed.

10. Use title tags. The use of headers is necessary because... they show the hierarchy of sections and give them more order.

12. No styles! Your html code should focus on structure and content, not page design! Place all styles in separate file CSS.

When faced with the need to supervise the work of other programmers, you begin to understand that, in addition to things that people learn quite easily and quickly, there are problems that require significant time to resolve.

Relatively quickly, you can train a person to use the necessary tools and documentation, correct communication with the customer and within the team, correct goal setting and prioritization (well, of course, to the extent that you own all this yourself).

But when it comes to the actual code, things become much less clear-cut. Yes, you can point to weak spots, you can even explain what’s wrong with them. And next time get a review with a completely new set of problems.

The profession of a programmer, like most other professions, has to be learned every day for several years, and, by and large, throughout one’s life. First, you master a set of basic knowledge in the amount of N semester courses, then you trample for a long time on various rake, adopt the experience of older comrades, study good and bad examples (for some reason, the bad ones are more common).

Speaking about basic knowledge, it should be noted that the ability to write beautiful professional code is something that, for one reason or another, basic knowledge categorically Excluded. Instead, in relevant institutions, as well as in books, we are told about algorithms, languages, OOP principles, design patterns...

Yes, you need to know all this. But at the same time, an understanding of what a decent code should look like usually appears already with practical (usually to one degree or another negative) experience under one’s belt. And provided that life “poked” you not only with juicy examples of bad code, but also with examples truly worthy of imitation.

This is where the whole difficulty lies: your idea of ​​\u200b\u200b“decent” and “beautiful” code is completely based on your personal experience over many years. Now try to convey this idea in a short time to a person with a completely different experience or even no experience at all.

But if the quality of the code written by people working with us is really important to us, then it’s still worth a try!

2. Why do we need beautiful code?

Usually when we are working on a specific software product, the aesthetic qualities of the code are not our primary concern.
Our productivity, the quality of implementation of functionality, the stability of its operation, the possibility of modification and expansion, etc. are much more important to us.

But are the aesthetic qualities of the code a factor that positively influences the above indicators?
My answer: yes, and at the same time, one of the most important!

This is so because beautiful code, regardless of the subjective interpretation of the concept of beauty, has the following (to one degree or another reducible to each other) most important qualities:

  • Readability. Those. the ability, by looking at the code, to quickly understand the implemented algorithm and evaluate how the program will behave in a particular particular case.
  • Controllability. Those. the ability to make the required amendments to the code in the shortest possible time, while avoiding various unpleasant predictable and unpredictable consequences.
Why these qualities are truly the most important, and how they contribute to improving the indicators indicated at the beginning of the paragraph, I am sure, is obvious to anyone who is involved in programming.

And now, in order to move from general words to specifics, let's do the opposite and say that it is readable and manageable code that is usually perceived by us as beautiful and professionally written. Accordingly, we will further focus on discussing how to achieve these qualities.

3. Three basic principles.

Moving on to presenting my own experience, I note that, while working on the readability and manageability of my own and other people’s code, I gradually came to the following understanding.

Regardless of the specific programming language and the tasks being solved, in order for a piece of code to sufficiently possess these two qualities, it must be:

  • as linear as possible;
  • short;
  • self-documented.
You can endlessly list various hints and techniques that can be used to make your code more beautiful. But I argue that the best, or at least fairly good results can be achieved by focusing on these three principles.

4. Code linearization.

It seems to me that out of three basic principles, it is linearity that is most unobvious, and it is it that is most often neglected.
Probably because over the years of study (and perhaps scientific activity) we are used to discussing naturally nonlinear algorithms with estimates like O(n3), O(nlogn) etc.

This is all, of course, good and important, but when talking about the implementation of business logic in real projects, we usually have to deal with algorithms of a completely different nature, more reminiscent of illustrations in children’s books on programming. Something like this (taken from):

Thus, with linearity I associate not so much the asymptotic complexity of the algorithm as the maximum number of code blocks nested within each other, or the level of nesting of the longest code subsection.

For example, a perfectly linear fragment:

Do_a(); do_b(); do_c();
And not at all linear:

Do_a(); if (check) ( something(); ) else ( anything(); if (whatever()) ( for (a in b) ( if (good(a)) ( something(); ) ) ) )
It is the “pieces” of the second type that we will try to rewrite using certain techniques.

Note: since here and further we will need code examples to illustrate certain ideas, we will immediately agree that they will be written in an abstract generalized C-like language, except in cases where the features of a specific existing language are required. For such cases, it will be explicitly indicated in what language the example is written (specifically, there will be examples in Java and Javascript).

4.1. Technique 1. Select the main branch of the algorithm.
In the vast majority of cases, it makes sense to take the longest successful linear scenario of the algorithm as the main branch.

Let's try to do this based on the “auto repair algorithm” in the diagram above:

  1. The client communicates his wishes.
  2. The master examines and tells the cost.
  3. Search for defects.
  4. We are placing an order for spare parts.
  5. We take an advance payment and set a deadline.
  6. The client leaves.
It is this main branch that we should ideally have at zero nesting level.

Listen_client(); if (!is_clean()) ( ... ) check_cost(); if (!client_agree()) ( ... ) find_defects(); if (defects_found()) ( ... ) create_request(); take_money(); bye();
For comparison, let's consider the option where at level zero there is an alternative branch, instead of the main one:

Listen_client(); if (!is_clean()) ( ... ) check_cost(); if (client_agree()) ( find_defects(); if (defects_found()) ( ... ) create_request(); take_money(); ) else ( ... ) bye();
As you can see, the level of nesting of a significant part of the code has increased, and looking at the code as a whole has become less pleasant.

4.2. Technique 2. Use break, continue, return or throw to get rid of the block else.
Badly:

If (!client_agree()) ( ... ) else ( find_defects(); if (defects_found()) ( ... ) create_request(); take_money(); bye(); )

If (!client_agree()) ( ... return; ) find_defects(); if (defects_found()) ( ... ) create_request(); take_money(); bye();

Of course, it would be wrong to conclude that you should never use the operator else. Firstly, the context does not always allow us to put break, continue, return or throw(although often it does allow it). Secondly, the benefit from this may not be as obvious as in the example above, and a simple else will look much simpler and clearer than anything else.

And thirdly, there are certain costs when using multiple return in procedures and functions, because of which many generally regard this approach as an anti-pattern (my personal opinion: usually the benefits still cover these costs).

Therefore, this (and any other) technique should be perceived as a hint, and not as an unconditional instruction to action.

4.3. Technique 3. We put complex sub-scenarios into separate procedures.
Because In the case of the “repair algorithm”, we quite successfully chose the main branch, but our alternative branches all remained very short.

Therefore, we will demonstrate the technique based on the “bad” example from the beginning of the chapter:

Do_a() if (check) ( something(); ) else ( anything(); if (whatever()) ( for (a in b) ( if (good(a)) ( something(); ) ) ) )
Better:

Procedure do_on_whatever() ( for (a in b) ( if (good(a)) ( something(); ) ) ) do_a(); if (check) ( something(); ) else ( anything(); if (whatever()) ( do_on_whatever(); ) )
Please note that by choosing the right name for the highlighted procedure, we also immediately increase the self-documentation of the code. Now for this fragment it should be clear in general terms what it does and why it is needed.

However, it should be borne in mind that the same self-documentation will suffer greatly if the piece of code taken out does not have an overall completed task that the code performs. Difficulty in choosing the correct name for a procedure may be an indicator of just such a case (see clause 6.1).

4.4. Technique 4. We take everything that is possible into the external unit, leaving only what is necessary in the internal one.
Badly:

If (check) ( do_a(); something(); if (whatever()) ( for (a in b) ( if (good(a)) ( something(); ) ) ) else ( do_a(); anything (); if (whatever()) ( for (a in b) ( if (good(a)) ( something(); ) ) ) )
Better:

Do_a(); if (check) ( something(); ) else ( anything(); ) if (whatever()) ( for (a in b) ( if (good(a)) ( something(); ) ) )

4.5. Technique 5 ( special case previous). Place in try...catch only what is necessary.
It should be noted that the blocks try...catch are generally a pain when it comes to code readability, because Often, overlapping each other, they greatly increase the overall level of nesting even for simple algorithms.

The best way to combat this is to minimize the size of the area inside the block. Those. all lines that do not expect an exception to occur must be moved outside the block. Although in some cases, from the point of view of readability, the strictly opposite approach may be more advantageous: instead of writing many small blocks try..catch, it is better to combine them into one big one.

In addition, if the situation allows you not to handle the exception here and now, but to throw it down the stack, it is usually better to do just that. But we must keep in mind that variability here is only possible if you yourself can set or change the contract of the procedure you are editing.

4.6. Technique 6. Combine nested ifs.
Everything is obvious here. Instead of:

If (a) ( if (b) ( do_something(); ) )
we write:

If (a && b) ( do_something(); )

4.7. Technique 7. Use the ternary operator ( a? b:c) instead of if.
Instead of:

If (a) ( var1 = b; ) else ( var1 = c; )
we write:

Var1 = a ? b:c;
Sometimes it makes sense to even write nested ternary operators, although this presupposes the reader's knowledge of the precedence with which subexpressions of the ternary operator are evaluated.

If (a) ( var1 = b; ) else if (aa) ( var1 = c; ) else ( var1 = d; )
we write:

Var1 = a ? b:aa? c:d;
But it’s probably not worth abusing this.

Note that variable initialization var1 is now carried out in one single operation, which again greatly contributes to self-documentation (see clause 6.8).

4.8. Summarizing the above, let's try to write a complete implementation of the repair algorithm as linearly as possible.
listen_client(); if (!is_clean()) ( wash(); ) check_cost(); if (!client_agree()) ( pay_for_wash(); bye(); return; ) find_defects(); if (defects_found()) ( say_about_defects(); if (!client_agree()) ( pay_for_wash_and_dyagnosis(); bye(); return; ) ) create_request(); take_money(); bye();
We could stop here, but it doesn’t look very cool that we have to call 3 times bye() and, accordingly, remember that when adding a new branch, it will have to be written before the return each time (in fact, the costs of multiple returns).

It would be possible to solve the problem through try...finally, but this is not entirely correct, because V in this case there is no talk about exception handling. And such an approach would greatly hinder the linearization process.

Let's do this (in fact, I used clause 5.1 even before I wrote it):

Procedure negotiate_with_client() ( check_cost(); if (!client_agree()) ( pay_for_wash(); return; ) find_defects(); if (defects_found()) ( say_about_defects(); if (!client_agree()) ( pay_for_wash_and_dyagnosis() ; return; ) ) create_request(); take_money(); ) listen_client(); if (!is_clean()) ( wash(); ) negotiate_with_client(); bye();
If you think that we have now recorded something trivial, then, in principle, it is so. However, I assure you that in many live projects you would see a completely different implementation of this algorithm...

5. Code minimization.

I think it would be superfluous to explain that by reducing the amount of code used to implement a given functionality, we make the code much more readable and reliable.

In this sense, the ideal engineering solution is when nothing is done, but everything works as required. Of course, in the real world, perfect solutions are rarely available, which is why we programmers still have work to do. But it is precisely this ideal that we should strive for.

5.1. Technique 1. Eliminate code duplication.
So much has already been said about copy-paste and the harm that comes from it that it would be difficult to add anything new. Nevertheless, programmers, generation after generation, intensively use this method to implement software functionality.

Of course, the most obvious method of dealing with the problem is to move the reused code into separate procedures and classes.
In this case, the problem of separating the general from the particular always arises. Often it is not even always clear what similar pieces of code have more similarities or differences. The choice here is made solely according to the situation. However, the presence of identical sections the size of half a screen immediately indicates that this code can and should be written much shorter.

It's also worth mentioning the very useful de-duplication technique described in clause 4.3.
It can be extended beyond just if statements. For example, instead of:

Procedure proc1() ( init(); do1(); ) procedure proc2() ( init(); do2(); ) proc1(); proc2();
let's write:

Init(); do1(); do2();
Or the opposite option. Instead of:
a = new Object(); init(a); do(a); b = new Object(); init(b); do(b);
let's write:
procedure proc(a) ( init(a); do(a); ) proc(new Object()); proc(new Object());

5.2. Technique 2. Avoid unnecessary conditions and checks.
Extra checks are an evil that can be found in almost any program. It is difficult to describe how the most trivial procedures and algorithms can be clogged with frankly unnecessary, duplicative and meaningless checks.

This especially applies, of course, to checks for null. As a rule, this is caused by the eternal fear of programmers of the ubiquitous NPE and the desire to once again be safe from them.

A not uncommon type of unnecessary check is the following example:

Obj = new Object(); ... if (obj != null) ( obj.call(); )
Despite their obvious absurdity, such checks occur with impressive regularity. (I’ll make a reservation right away that the example does not apply to those rare languages ​​and environments where the operator new() can return null. In most cases (including in Java), this is basically impossible).

Here you can also include dozens of other types of checks for a condition that is obviously met (or obviously not met).
Here, for example, is the following case:

Obj = factory.getObject(); obj.call1(); // if obj were null, we would already be dead) if (obj != null) ( obj.call2(); )
It occurs many times more often than the previous type of checks.

The third example is a little less obvious than the first two, but is simply ubiquitous:

Procedure proc1(obj) ( if (!is_valid(obj)) return; obj.call1(); ) procedure proc2(obj) ( if (!is_valid(obj)) return; obj.call2(); ) obj = factory. getObject(); if (is_valid(obj) ( proc1(obj); proc2(obj); )
As you can see, the author of this segment is terrified of running into an invalid object, so he checks it before every sneeze. Although such a strategy may sometimes be justified (especially if proc1() And proc2() exported as an API), in many cases this is simply code clutter.

The options here may be different:

  • For each procedure, define an input contract that requires the caller to meet certain validity conditions. After this, remove the safety checks and transfer the entire responsibility for the validity of the input data to the calling code. For Java, for example, it might be good practice to mark “dangerous” methods and fields with an annotation @Nullable. Thus, we implicitly indicate that other fields and methods can accept/return a value null.
  • Do not create invalid objects at all! This approach involves performing all validation procedures at the object creation stage, so that the fact itself successful creation has already guaranteed validity. Of course, this at a minimum requires that we control the factory class or other mechanisms for creating objects. Or so that the existing implementation gives us such a guarantee.
Perhaps, at first glance, these options seem impossible or even scary, however, in reality, with their help you can significantly increase the readability and reliability of the code.

Another useful approach is to use a pattern NullObject, which involves using an object with methods that do nothing, but also do not cause errors, instead of a “dangerous” one null. A special case of this approach can be considered the refusal to use null for collection variables in favor of empty collections.

This also includes special null-safe libraries, among which I would like to highlight a set of libraries apache-commons for Java. It allows you to save great amount place and time, eliminating the need to write endless routine checks on null.

5.3. Technique 3. Avoid writing “bicycles”. We use ready-made solutions to the maximum.
Bicycles are almost like copy-paste: everyone knows it's bad, and everyone writes them regularly. We can only advise you to at least try to fight this evil.

Most of the time we are faced with tasks or subtasks that have already been solved many times, be it sorting or searching through an array, working with 2D graphics formats or long-polling servers in Javascript. The general rule is that standard problems have a standard solution, and this solution allows us to get the desired result by writing a minimum of our code.

Sometimes there is a temptation, instead of looking for something, trying and adjusting, to quickly sketch out your bike on your knee. Sometimes this can be justified, but if we are talking about long-term maintained code, a minute of “saving” can turn into hours of debugging and fixing errors on missed corner cases.

On the other hand, just touching on a fairly broad topic, I would like to say that sometimes a “bicycle” implementation can or even should be preferred to using a ready-made solution. This is usually true in the case where the trust in the quality of the finished solution is no higher than in the native code, or in the case when the costs of introducing a new external dependency are technically unacceptable.

However, returning to the issue of code brevity, of course, using standard ones (for example, apache-commons And guava for Java) and non-standard libraries is one of the most effective ways reduce the size of your own code.

5.4. Technique 4. We leave in the code only what is actually used.
“Dangling” functions that are not called by anyone anywhere; sections of code that are never executed; entire classes that are not used anywhere, but they forgot to remove them - I’m sure everyone could observe such things in their project, and maybe even took them for granted.

In fact, any code, including unused ones, requires payment for its maintenance in the form of spent attention and time.
Because unused code is not actually executed or tested, it may contain incorrect calls to certain procedures, unnecessary or incorrect checks, calls to procedures and external libraries that are no longer needed, and a variety of other confusing or simply harmful of things.

Thus, by removing unnecessary and unused areas, we not only reduce the size of the code, but also, just as importantly, contribute to its self-documentation.

5.5. Technique 5. We use our knowledge of the language and rely on the reader to have this knowledge.
One of the effective ways to make your code simpler, shorter and clearer is the skillful use of features of a specific language: various defaults, operation priorities, short forms, etc.

As an illustration, I will give the most, from my point of view, striking example for Javascript language.

Very often, when parsing string expressions, you can see the following piles:
if (obj != null && obj != undefined && obj.s != null && obj.s != undefined && obj.s != "") ( // do something )
It looks scary, including from the point of view of “did the author forget some other check.” In fact, knowing the peculiarities of the Javascript language, in most such cases the entire check can be reduced to trivial:

If (obj && obj.s) ( // do something )
The point is that thanks to the implicit cast to boolean, the check if (obj) () will weed out:

  • false
  • null
  • undefined
  • empty line
In general, it will eliminate most of the trivial meanings that we can imagine. Unfortunately, I repeat, programmers use this feature quite rarely, which is why their “reinsurance” checks look very cumbersome.

Likewise, compare the following notation forms:

If (!a) ( a = defaultValue; )
And

A = a || defaultValue;
The second option looks simpler, thanks to the use of specific semantics of logical operations in scripting languages.

6. Self-documenting code.

The term “self-documenting” is most often used to describe the properties of formats such as XML or JSON. In this context, it is implied that the file contains not only a set of data, but also information about its structure, the names of entities and fields specified by this data.

Reading XML file we, even without knowing anything about the context, can almost always get an idea of ​​what it describes this file what kind of data is presented in it and even, perhaps, how it will be used.

Extending this idea to program code, under the term “self-documentation” I would like to combine many properties of the code that allow you to quickly, without detailed analysis and deep understanding of the context, understand what this code does.

I would like to contrast this approach with “external” documentation, which can be expressed in the presence of comments or separate documentation. Without denying the need for both in certain cases, I note that when it comes to code readability, self-documenting methods are much more effective.

6.1. Technique 1. Carefully choose the names of functions, variables and classes. You shouldn't deceive people who will read our code.
The most important rule that should be taken as a basis when writing self-documenting code is never deceive your reader.

If the field is called name, the name of the object should be stored there, and not the date of its creation, serial number in the array or the name of the file into which it is serialized. If the method is called compare(), it should compare objects, and not put them into a hash table, access to which can be found somewhere 1000 lines lower in the code. If the class is called NetworkDevice, then its public methods should contain operations applicable to the device, and not a general implementation of quicksort.

It is difficult to express in words how often, despite the obviousness of this rule, programmers break it. I don’t think it’s worth explaining how this affects the readability of their code.

To avoid such problems, it is necessary to think through the name of each variable, each method and class as carefully as possible. At the same time, we must try not only correctly, but also, if possible, as much as possible. full describe the purpose of each design.

If this cannot be done, the cause is usually small or gross mistakes design, so this should be taken at least as an alarm bell.

Obviously, it is also worth minimizing the use of named variables i, j, k, s. Variables with such names can only be local and have only the most generally accepted semantics. When i, j, these could be loop counters or indexes in an array. Although, if possible, it is worth getting rid of such counters in favor of foreach loops and functional expressions.
Variables with names ii, i1, ijk42, asdsa etc. should never be used. Well, maybe if you work with mathematical algorithms... No, it’s better never.

6.2. Technique 2. We try to call identical things the same, and different ones differently.
One of the most discouraging difficulties that arise when reading code is the synonyms and homonyms used in it. Sometimes in a situation where two different entities are named the same, you have to spend several hours to separate all the cases of their use and understand which of the entities is meant in each specific case. Without such a separation, normal analysis, and therefore meaningful modification of the code, is impossible in principle. And such situations occur much more often than one might expect.

Approximately the same can be said about the “reverse” problem - when several different names are used for the same entity/operation/algorithm. The time to analyze such code may increase significantly compared to expected.

The conclusion is simple: in your programs you should treat the emergence of synonyms and homonyms extremely carefully and try your best to avoid this.

6.3. Technique 3. “Occam’s razor”. We don’t create entities that we can do without.
As already mentioned in paragraph 5.4., any piece of code, any object, function or variable that you create, in the future, during the support process, will require payment in the form of your (or someone else’s) time and attention.

The most direct conclusion follows from this: the fewer entities you enter, the simpler and better your code will end up being.

A typical example of an “extra” variable:

Int sum = counSum(); int increasedSum = sum + 1; operate(increasedSum); ...
Obviously the variable increasedSum is an extra entity, because description of the object it stores (sum + 1) characterizes a given object much better and more accurately than the name of a variable. Thus, the code should be rewritten as follows (“inline” the variable):

Int sum = counSum(); operate(sum + 1); ...
If the amount is not used anywhere further in the code, you can go further:

Operate(countSum() + 1); ...
Inlining unnecessary variables is one way to make your code shorter, simpler and clearer.

However, it should be used only if this technique promotes self-documentation and does not contradict it. For example:

Double descr = b * b - 4 * a *c; double x1 = -b + sqrt(descr) / (2 * a);
In this case, the inline variable descr is unlikely to benefit readability, because this variable is used to represent a specific entity from subject area, and, therefore, the presence of a variable contributes to the self-documentation of the code, and the variable itself does not fall under Occam’s razor.

Summarizing the principle demonstrated in in this example, we can conclude the following: it is advisable to create variables/functions/objects in your program only if they have prototypes in the subject area. At the same time, in accordance with clause 6.1, we must try to ensure that the name of these objects expresses this correspondence as clearly as possible. If there is no such correspondence, it is quite possible that using a variable/function/object only overloads your code, and removing them will only benefit the program.

The general principle is simple: any explicitly written algorithms or conditions are already self-documenting, since their purpose and operating principle have already been described by themselves. On the contrary, any indirect conditions and operations with side effects make it very difficult to understand the essence of what is happening.

You can give a rough example, implying that life throws up such examples quite often.

Indirect condition:

If (i == abs(i)) ( )
Direct condition:

If (i >= 0) ( )
I think it’s not difficult to assess the difference in readability.

6.5. Technique 5. Everything that can be hidden in private (protected) should be hidden there. Encapsulation is everything to us.
I consider it unnecessary to talk about the benefits and necessity of following the principle of encapsulation when writing programs in this article.
I would just like to emphasize the role of encapsulation as a mechanism for self-documenting code. First of all, encapsulation allows you to clearly highlight the external interface of a class and designate its “entry points,” i.e., methods in which execution of the code contained in the class can begin. This allows the person studying your code to save a huge amount of time by focusing on the functionality of the class and abstracting away from the implementation details.
6.6. Technique 6. (Generalization of the previous one) We declare all objects in the narrowest possible scope.
The principle of maximally limiting the scope of each object can be extended wider than the usual OOP encapsulation.

Simple example:

Object someobj = createSomeObj(); if (some_check()) ( // Don't need someobj here ) else ( someobj.call(); )
Obviously, such a declaration of the someobj variable makes it difficult to understand its purpose, since the reader of your code will look for calls to it in a much wider area than it is used and actually needed.

It's not hard to figure out how to make this code a little better:

If (some_check()) ( // Don't need someobj here ) else ( Object someobj = createSomeObj(); someobj.call(); )

Well, or, if the variable is needed for a single call, you can also use clause 6.3:

If (some_check()) ( // Don't need someobj here ) else ( createSomeObj().call(); )
Separately, I would like to mention the case when this technique may not work or work to its detriment. These are variables that are initialized outside of loops. For example:

Object someobj = createSomeObj(); for (int i = 0; i< 10; i++) { someobj.call(); }
If creating an object via createSomeObj()- an expensive operation, introducing it into a loop can have an unpleasant effect on program performance, even if it improves readability.

Such cases do not invalidate the general principle, but simply serve to illustrate that each technique has its own area of ​​application and should be used thoughtfully.

6.7. Technique 7. We clearly separate the static and dynamic context. Methods that do not depend on the state of the object must be static.
Confusion or lack of separation between static and dynamic contexts is not the worst, but a very common evil.

It leads to the appearance of unnecessary dependencies on the state of the object in potentially static methods, or generally to incorrect management of the object’s state. As a result, it becomes difficult to analyze the code.

Therefore, it is necessary to try to pay attention to this aspect, clearly highlighting methods that do not depend on the state of the object.

6.8. Technique 8. We try not to separate the declaration and initialization of the object.
This technique allows you to combine a declaration of an object's name with an immediate description of what the object is. This is a clear example of following the principle of self-documentation.

If this technique is neglected, the reader will have to search for each object in the code, where it was declared, whether it is initialized somewhere with a different value, etc. All this makes it difficult to analyze the code and increases the time required to understand the code.

That is why, for example, functional operations from apache CollectionUtils And guava Collections2 often preferable to Java built-ins foreach loops - they allow you to combine the declaration and initialization of a collection.

Let's compare:

lowercaseStrings = new ArrayList (); /* collection is uninitialized here, need to investigate the initializion */ for (String s: somestrings) ( lowercaseStrings.add(StringUtils.lowerCase(s)); )
c:
// getting “somestrings” collection somehow ... Collection lowercaseStrings = Collections2.transform(somestrings, new Function () ( @Override public String apply(String s) ( return StringUtils.lowerCase(s); ) )));
If we use Java 8, we can write it a little shorter:

Collection lowercaseStrings = somestrings.stream() .map(StringUtils::lowerCase).collect(Collectors.toList());
Well, it’s worth mentioning the case when you somehow have to separate the declaration and initialization of variables. This is a case of using a variable in blocks finally And catch(for example, to release some resource). There is nothing left to do here except declare the variable before try, and initialize inside the block try.

6.9. Technique 9. We use a declarative approach and functional programming tools to indicate, rather than hide, the essence of what is happening.
This principle can be illustrated by the example from the previous paragraph, in which we used emulation of the functional approach in Java in order to make our code clearer.

For greater clarity, let’s also consider an example in Javascript (taken from here: http://habrahabr.ru/post/154105).

Let's compare:

Var str = "mentioned by"; for(var i =0; l= tweeps.length; i< l; ++i){ str += tweeps[i].name; if(i< tweeps.length-1) {str += ", "} }
c:

Var str = "mentioned by " + tweeps.map(function(t)( return t.name; )).join(", ");
Well, examples of using the functional approach that kill readability... Let's save our nerves this time and do without them.

6.10. Technique 10. We write comments only if without them it is not at all clear what is happening.
As mentioned above, the principle of self-documenting code is opposed to documenting using comments. I would like to briefly explain why comments are so bad:
  • Well-written code doesn't need them.
  • If maintaining code often becomes a labor-intensive task, usually no one is involved in maintaining comments, and after a while comments become outdated and begin to deceive people (“comments lie”).
  • They clog up space, thereby worsening appearance code and making it difficult to read.
  • In the vast majority of cases, they are written personally by Captain Obvious.
Well, comments are needed in cases where it is not possible to write good code for one reason or another. Those. They should be written to explain sections of code that would be difficult to read without them. Unfortunately, in real life This happens regularly, although it should only be regarded as an inevitable compromise.

Also, as a type of such a compromise, one should consider the need to specify a method/class/procedure contract using comments if it cannot be explicitly expressed by other language means (see clause 5.2).

7. Philosophical conclusion.

Having finished presenting the basic techniques and techniques with which you can make your code a little more beautiful, you should once again clearly formulate: none of the techniques is an unconditional guide to action. Any technique is only a possible means of achieving a particular goal.

In our case, the goal is to make the code as readable and manageable as possible. Which at the same time will be pleasant from an aesthetic point of view.

Studying the examples given in the current article, one can easily notice that both the original principles themselves (linearity, minimality, self-documentation) and specific techniques are not independent of each other. By using one technique, we can also indirectly follow a completely different one. By improving one of the target indicators, we can contribute to the improvement of others.

However, this phenomenon also has a downside: there are often situations when the principles come into direct conflict with each other, as well as with many other possible rules and dogmas of programming (for example, with the principles of OOP). You need to take this completely calmly.
There are also cases when it is clear good decision this or that problem does not exist at all, or this solution is not feasible for a number of reasons (for example, the customer does not want to accept potentially dangerous changes in the code, even if they contribute to an overall improvement in quality).

In programming, as in most other areas of human activity, there cannot be universal instructions for execution. Each situation requires separate consideration and analysis, and the decision must be made based on an understanding of the specifics of the situation.

In general, this fact does not in any way negate the usefulness of both the derivation and understanding of general principles, and the possession in specific ways their implementation. That is why I hope that everything stated in the article can be really useful for many programmers.

Well, a couple more words about where we started - about the beauty of our code. Even if you know about “advanced” techniques for writing beautiful code, you shouldn’t neglect the most simple things, such as basic auto-formatting and following the coding style established in the project. Often formatting alone can do wonders for an ugly piece of code. As well as intelligent grouping of code sections using empty lines and breaks. Add tags







2024 gtavrl.ru.