The Good, the Bad and the Ugly code


    Good code or bad? For me personally, a good code has the following qualities:
    • The code is easy to understand for developers of various skills and well structured
    • Code is easy to modify and maintain.
    • The application performs its functions and has sufficient fault tolerance for the current range of tasks

    Despite a short description of how to achieve these three conditions, many thick books have been written.

    Why exactly these criteria? Immediately make a reservation, we are now talking about developing software for business (enterprise application) . Code evaluation criteria for real-time systems, aircraft, life support systems and the ISS are different.


    Surely everyone or almost everyone knows this triangle.
    At the same time, of the three conditions - quickly, efficiently, cheaply - only two can be fulfilled.
    When developing business applications, our goal is to develop good enough software for an acceptable period, keeping within the budget. In practice, this means that there may be errors in our application, but in places that are not critical to the system. Let's look at a few examples to better understand what this means.

    Case No. 1


    On the form there is a text input field (textarea). You can enter 100500 characters there and our application will fall down with the exception “the maximum request length has been exceeded”.

    Is this our case? Hardly. If this is the public part, it is worth putting a Java-script validator and not allowing such forms to post. Setting up an additional server for those who have disabled java-script is just a waste of time, which means budget. We will not do this. Yes, there is a potential error in our application. Does anyone need to be fixed? Not.

    Case No. 2


    A request comes from the client: we need a partners page. This page will contain brief information about each of them and a link to the page on which we must embed the page from the partner’s site in the iframe.

    An interesting case. We probably need to make a change to the database, add the partner entity, we don’t pass the db-entity by design to the View, so we also need a DTO, a mapping function from db-entity to dto, and a database migration for auto deploy. Hmm, we still need a page in the admin panel to add a partner. Do not forget about routing for the controller, so that / Partner /Quite a lot of work ...

    True for 10,000 partners. Let's ask: “how many partners will be” ? If the answer is "this year is three." We should reconsider our views. This is again a waste of time and budget. Only 3 partners?


    namespace Habrahabr.Models.Partner
    {
        public class PartnerViewModel
        {
            public string Name { get; set; }
            public string WebSiteUrl { get; set; }
            public string ShortDescription { get; set; }
            public string FullDescription { get; set; }
        }
    }
    using System.Web.Mvc;
    using Habrahabr.Models.Partner;
    namespace Habrahabr.Controllers
    {
        public class PartnerController : Controller
        {
            private static PartnerViewModel[] partners = new[]
    	 {
    		 new PartnerViewModel()
    			 {
    				 Name = "Coca-Cola",
    				 WebSiteUrl = "http://coca-cola.com/partner",
    				 ShortDescription = "Coca-cola short description", 
    				 FullDescription = "Coca-cola full description"
    			 },
    		 new PartnerViewModel()
    			 {
    				 Name = "Ikea",
    				 WebSiteUrl = "http://ikea.com/partner",
    				 ShortDescription = "Ikea short description", 
    				 FullDescription = "Ikea full description"
    			 },
    		 new PartnerViewModel()
    			 {
    				 Name = "Yandex",
    				 WebSiteUrl = "http://yandex.ru/partner",
    				 ShortDescription = "Yandex short description", 
    				 FullDescription = "Yandex full description"
    			 }
    	 };
            public ActionResult Index()
            {
                // TODO: populate partners from database if a lot of partner required
                return View(partners);
            }
            // TODO: refactor this if a lot of partner required
            [ActionName("Coca-Cola")]
            public ActionResult CocaCola()
            {
                return View("_PartnerDetail", partners[0]);
            }
            public ActionResult Ikea()
            {
                return View("_PartnerDetail", partners[1]);
            }
            public ActionResult Yandex()
            {
                return View("_PartnerDetail", partners[2]);
            }
        }
    }
    @model IEnumerable
      @foreach (var p in @Model) {
    • @Html.ActionLink(p.Name, p.Name, "Partner")

      @p.ShortDescription

      @Html.ActionLink("Partner page", p.Name, "Partner")

    • }
    @model Habrahabr.Models.Partner.PartnerViewModel

    @Model.Name

    @Model.FullDescription
    @if (!string.IsNullOrEmpty(Model.WebSiteUrl)) { }




    For everything about everything - a maximum of an hour of time, taking into account the input of texts. We have developed a fairly good partner page. Does it meet our criteria:
    • Clear and structured - yes
    • Easy to change - yes
    • Reliable enough - yes

    Domain, data access and application architecture


    Fowler correctly noted in his book Enterprise Patterns of Enterprise Application Architecture that the term “architecture” is incredibly blurred:
    The term "architecture" is trying to be interpreted by everyone who is not lazy, and everyone in their own way. However, there are two general options. The first is related to the division of the system into the largest components; in the second case, some constructive decisions are meant, which, after their adoption, are difficult to change. There is also a growing understanding that there is more than one way to describe architecture and the degree of importance of each of them changes throughout the life cycle of the system.

    The above described approach with partners has one significant limitation. You cannot constantly write application code in this style. Sooner or later, the system will grow. In this case, we “sewed up” the application logic. A couple more of these controllers and understand the system will be quite difficult, especially for new developers.
    At the moment when it turns out that there will be not 3 partners, but 100,500 and not all should be shown, but only those who paid on the night of Friday to Saturday (from 13 to 14) and sacrificed 6 virgins, and the pages should be rotated in correspondence with the position of Venus regarding Jupiter (in real life, the cases, of course, are different, but it is not for nothing that they say that for the developer there is nothing more illogical than the “business logic”, which consists entirely of exceptions and special cases). You need to think about architecture in both senses of the word.

    We left the “seams” in the application in advance, we will begin to rip them apart and sew the necessary pieces. We will use ORM , rename PartnerViewModel to Partner and map the partner class to the database. I never used Database-First and did not take Entity Framework seriously until the version with Code-First approach was released. In this case, I do not see the need to map Partner in PartnerViewModel . Yes, semantically these entities perform different tasks and in general, PartnerViewModel may differ, but so far there is not a single reason to write exactly the same class. Map ViewModelit makes sense when you can’t do without it without dragging the logic into the view. Usually, the need for ViewModels arises if the interface needs to show a complex form that operates with several entities of the domain at once, or there is a need to use attributes from the System.Web assembly for model properties. In the latter case, I am inclined to drag this dependency into an assembly with a subject area if I am sure that in the next six months there will not be a new entry point in the application, except for the main web application.

    In one project in which I participated, to access the data it was necessary to call a service that accessed the facade, which in turn accessed the DAL layer that called the Entity Framework. The façade and the DAL were in different assemblies, and EF used the Database-First approach . DAL usually performed all the logic , and the rest of the layers simply remap the results in 90% of cases into absolutely identical DTOs. Moreover, the system had no other clients and the service was objectively unnecessary. When I asked "why such an overhead", the developer who worked on the project longer said: "I don’t know, our other project is written like this, we decided to do it by analogy." Yes, this “other project” consisted of 20 applications and it took only 2 days to deploy it. The API of this application has many clients. Such a complex structure was a necessity. In our case, it was shooting from a cannon at sparrows.

    But back to our ramsto partners. It remains to understand where to write the code responsible for choosing the necessary partners for display. I remind you that now we must consider Venus and Jupiter. The worst place is the controller. By doing so, we scatter the logic of the application into its various parts, and new developers will have to collect it (logic), like Harry Potter, who collected the crucifixes .
    A good option is to use the specification repository pattern .

    IOC / DI


    With the growth of the application, there is a danger of making it too tightly connected, and this will make the system slow and will interfere with code changes.

    I will not once again describe what IOC / DI is and what it is eaten with, you just need to take it as a rule:
    • Do not explicitly create dependencies
    • Use IOC containers

    It is not necessary to create an interface for each class in the project. Often, the interface can be easier to highlight later. R # does a great job of this. But keep this in mind when you write a class. Think of it as an interface, not a specific implementation. Using DI you can flexibly manage implementations. If necessary, you can throw out one implementation and replace it with another, for example, if for some reason the database stops working for you, you can transparently replace it by replacing the repository implementation. In addition, using IOC / DI is much easier to write test code.

    Tests


    I am not a fan of 90-100% coverage of the project. I think that more often than not, this slows down development and makes code support a much more dreary undertaking. I prefer to test the behavior of the system, i.e. basic business rules of the application. Thus, the tests not only insure against errors, but also help new developers to quickly understand the logic of the system. Such tests work better than any documentation. Tests using BDD notation are especially effective.

    Further development of the system


    If the application continues to grow, it’s hard to give specific advice: there are too many factors to consider. Too much depends on the specifics of the project. A good solution might be SOA . In general, decomposition of any large system is a great idea. Ten projects are uploaded to your IDE or a hundred - the difference is very big. When developing software of this scale, deploy-scripts, installers and release management are indispensable, but this is a completely different story ...

    Lambdas, aspects, functionality and other holivor


    In the end, I decided to leave deliberately controversial moments. I noticed that many developers tend to get used to the stack with which they work. If the programmer is "used" to his stack, he is inclined to drag it into each new project, regardless of its usefulness. So, I have repeatedly heard criticism of C # for “flirting” with a functional style, lambdas that are compiled to “damn know what” and “don't debug” and for other extension methods. Objectively, we see that in php we added closures, in java8 Stream will appear for streaming processing collections, which means many developers like this.

    Do not be afraid to combine paradigms, if it is worth it. Ask yourself the questions “how much platform / language / os are suitable for the task?” And “what technologies will help me complete the task as efficiently as possible?”. My point is best illustrated by an iliakan quote :
    I figured it would be faster to learn Erlang or get Java to work fast enough and decided to learn Erlang

    Books on the topic



    Also popular now: