JavaRush /Java Blog /Random EN /Coffee break #72. Code quality control and why you need i...

Coffee break #72. Code quality control and why you need it. What is a monad? Basic Theory for Java Developer

Published in the Random EN group

Code Quality Control and why you need it

Source: DZone Code quality is important, right? Everyone knows this and everyone agrees with it. But why then do most software companies:
  • do not have a definition of code quality,
  • inconsistently use code quality standards,
  • do not control the quality of their code?
In this article, I'm going to focus on the third problem, which is the lack of control over code quality . Coffee break #72.  Code quality control and why you need it.  What is a monad?  Basic Theory for Java Developer - 1If the team does not control the quality of the source code, how does the team determine that the developers are really creating effective software? Poor code quality degrades performance and workflow efficiency. Moreover, are they aware that their technical debt may be increasing? And, as a result, the cost of adding new features will increase.

Technical debt?

Hundreds of texts have been written about technical debt, but this one by Martin Fowler covers the topic well. So, what happens if you don't pay off your technical debt (or, in other words, what happens if you don't work on improving the code)? If you take poor care of your code , then this leads to the following consequences:
  1. The code does not meet modern coding standards.
  2. The quality of your code suffers.
  3. There are problems with testing your code.
  4. You have to spend extra time on improving the quality of the code (refactoring).
  5. You spend more time and effort on code maintenance than on development.
You may not notice these problems at first, but over time everything will become clear. “Like with monetary debt, if technical debt is not repaid, it can accumulate ‘interest’, making it difficult to implement change.” It was kind of a financial look at the consequences of poor code quality. But let's look at this problem from the point of view of software developers. What does poor code quality and high technical debt mean to us ?
  1. Increased stress levels , as you may not be in time for the deadline. It takes more and more time to add something to the code.
  2. Again, an increase in stress levels , because on the weekend you have to deal with code problems.
  3. Tiresome and boring work instead of creative and enjoyable coding.
  4. "Why does it take so long?" is a question that your leadership is repeating more and more often.
  5. The frustration of working with bad code . You know this, don't you? ;)
Okay, now we know how bad it can be if we don't care enough about our code. But what can we do about it?

Code Quality Control Process

I'm pretty sure you know that there is no magic bullet that can turn bad code into good code. Especially in one day. But that doesn't mean we can't do anything. We can build a process . It can be a series of iterative steps that:
  • help maintain code quality at a high level for projects where the quality is already satisfactory;
  • show what, where and how to change in the existing code in order to improve its quality.
However, doing everything manually is not the best option. First, it's inefficient. Second, errors can occur. Have you ever overlooked an obvious error in your code while reviewing the code? For example, yes… :( Moreover, sometimes bugs are not even detected during testing (manual, of course), but appear on the very first day after the release...

Automation is the key to success

Manual code review is worse than automatic code review. Automation can take the burden of constantly checking your code off of you. This will allow you to focus on solving real problems with the software rather than solving the problems of the software itself.. There are many tools in the Java development world that can help you automate code review and improve code quality. I don't know of any tool that can transform your outdated, poorly designed and poorly coded project into a well organized and easily maintainable one. At least not now. And not everything can be fully automated (for example, code reviews). But can we get rid of many hard-to-find code bugs and make our code cleaner and more reliable at the very beginning of the development cycle ( SDLC )? More importantly, by automating the process, we can achieve this with only a small amount of effort and time compared to manual work. And all this before the test team gets down to business. ;)

What to check?

Now we know that we need a process. Moreover, we know that it should be as automatic as possible. But what should we check? We can divide the entire source code review process into 7 areas:
  • code style check
  • checking the correctness of the code and identifying typical errors,
  • identifying design flaws
  • code complexity calculation,
  • security check of code and dependencies,
  • code coverage calculation,
  • code review.

Conclusion

Code quality is a very important topic and there are many different opinions on it. But whatever you think about the quality of the code, it still affects our (developers) life. It can improve our quality of life or worsen it. You can be proud of your code, or you can be ashamed of it. You can solve real problems or perform tedious and repetitive tasks every day. It is not enough to educate developers, organize training sessions, or make presentations about code quality. Having a code quality assurance process is a must if a company is serious about building software. Even in the "star" development teams.

What is a monad? Basic Theory for Java Developer

Source: DZone As the title suggests, this article will focus on monads. Using Java's Optional class, I will try to describe them in detail, diving into the structure and inner workings of monads. At the end of the post, I will implement a logging monad, and then I will describe the role of each main piece of code and give a simple example of its use.Coffee break #72.  Code quality control and why you need it.  What is a monad?  Basic Theory for Java Developer - 2

Why study how monads work?

It's always good to have a basic understanding of how the things we use work. If you are a Java developer, then you are probably using monads without even knowing it. Two of the most well-known Java 8 features are monad implementations, namely Stream and Optional. Let's start by describing what a monad is. In my opinion, everything is quite simple here.
"A monad is just a monoid in the category of endofunctors."
It was a quote from Categories for Practicing Mathematicians by Saunders McLane. Now let's be serious...

What is a monad?

It's a concept, not a class or interface. Of course, in the future it can be implemented as a class or interface. This can be done in almost any statically typed language with support for generic types. Moreover, we can think of it as a wrapper that puts our value in some context and allows us to perform operations on the value. In this context, the output of an operation in any stage is the input to the operation in the next stage. Examples of monads in modern programming languages:
  • Stream (Java).
  • Optional/Option (Java/Scala).
  • Either (Scala).
  • Try (Scala).
  • IO Monad (Haskell).

Monad Rules

The last thing to remember when talking about monads is their rules. If we want to treat our implementation as a true monad, we must follow them. There are three rules: left identity , right identity , and associativity . With the help of the Optional class , I will try to explain these rules in more detail. But first some definitions:
  • F is a signed function: (T -> Optional<U>) = Optional<U> .
  • G is a function with a signature (A -> Optional<B>) = Optional<B> .
  • FG = F.apply(value).flatMap(G) with caption: (T -> Optional<B>) = Optional<B> .

Left Identity

If we create a new monad and bind it to a function, the result should be the same as when we apply the function to a value:
Optional.of(value).flatMap(F).equals(F.apply(value))

Right Identity

The result of binding a single function to a monad should be the same as when creating a new monad:
Optional.of(value).flatMap(Optional::of).equals(Optional.of(value))

Associativity

In the chain of functional applications, it doesn't matter how the functions are nested:
Optional<B> leftSide = Optional.of(value).flatMap(F).flatMap(G)

Optional<B> rightSide = Optional.of(value).flatMap(F.apply(value).flatMap(G))

leftSide.equals(rightSide).

Creating a monad

The first thing we need is a parameterized type M<T>, a wrapper for our value of type T. Our type must implement two functions:
  • Unit - used to wrapper our value and has a signature (T) = M<T> .
  • Bind - responsible for performing operations. Here we are passing a function that operates on the value in our context and returns it with a different type already in the context. This method must have the following signature (T -> M<U>) = M<U> .
To make things clearer, I'll use Optional again and show how the structure above looks like in this case. Here, the first condition is met immediately because Optional is a parameterized type. The role of the unit function is performed by the ofNullable and of methods . FlatMap plays the role of the bind function . Of course, in the case of Optional , type bounds allow us to use more complex types than in the definition above.

Having finished with the theory, let's start implementation

//imports
public final class LogMonad<T> {

    private final T value;

    private static final Logger LOGGER =
      Logger.getLogger(LogMonad.class.getName());

    private LogMonad() {
        this.value = null;
    }

    private LogMonad(T value) {
        this.value = value;
    }

    static <T> LogMonad<T> of(T value) {
        Objects.requireNonNull(value, "value is null");
        return new LogMonad<>(value);
    }

    <U> LogMonad<U> flatMap(Function<T, LogMonad<U>> function) {
        Objects.requireNonNull(function, "function is null");
        try {
            return function.apply(value);
        } catch (Throwable t) {
            return new LogMonad<>();
        }
    }

    LogMonad<T> log() {
        LOGGER.log(Level.INFO, "{0}", value);
        return this;
    }

    LogMonad<T> log(Level loggingLevel) {
        Objects.requireNonNull(loggingLevel, "logging level is null");
        LOGGER.log(loggingLevel, "{0}", value);
        return this;
    }
//equals, hashCode & toString
}
And voila, the monad is implemented. Let's describe in detail what exactly I did here.

What exactly happened here

The basis of our implementation is a parameterized class with an immutable field named value , which is responsible for storing our value. The second field, Logger , will be responsible for the key effect of our monad. Then we have a private constructor that makes it impossible to create an object in any other way than with our add-in method. Next, we have two main monad functions, namely of (equivalent to unit ) and flatMap (equivalent to bind) that ensure that our implementation meets the required conditions in the form of monad laws. The last two methods are responsible for our monad effect, that is, they are responsible for writing the current value to standard output. One of them allows you to pass the logging level, the other uses the "INFO" level. Now it's time for the use case. So, here it is.
//imports
public final class LogMonad<T> {

    private final T value;

    private static final Logger LOGGER =
      Logger.getLogger(LogMonad.class.getName());

    private LogMonad() {
        this.value = null;
    }

    private LogMonad(T value) {
        this.value = value;
    }

    static <T> LogMonad<T> of(T value) {
        Objects.requireNonNull(value, "value is null");
        return new LogMonad<>(value);
    }

    <U> LogMonad<U> flatMap(Function<T, LogMonad<U>> function) {
        Objects.requireNonNull(function, "function is null");
        try {
            return function.apply(value);
        } catch (Throwable t) {
            return new LogMonad<>();
        }
    }

    LogMonad<T> log() {
        LOGGER.log(Level.INFO, "{0}", value);
        return this;
    }

    LogMonad<T> log(Level loggingLevel) {
        Objects.requireNonNull(loggingLevel, "logging level is null");
        LOGGER.log(loggingLevel, "{0}", value);
        return this;
    }
//equals, hashCode & toString
}
In the code above, in addition to seeing how monads work, we can also appreciate a few benefits of using them. First of all, we encapsulate our logging side effect in a monadic context. This provided a layer of abstraction over our logic. With this abstraction, we were able to reduce the amount of boilerplate. Our code has become more declarative, easier to read and understand. Finally, we have combined all operations into a single pipeline. On the other hand, in the non-monadic part of the example, we can see all the implementation details. The code is less descriptive and can be reused. Moreover, we have identified a side effect that may cause some problems in the future and made our code less readable. If we ever decide to handle an error due to a function call, we will need a lot of templates. In my opinion,

Summing up

A monad is a very useful concept. Probably many of us use it in our daily lives. In this article, I have tried to give a simple explanation of its theoretical basis and the logic behind the concept. I've implemented LogMonad to show that it's super complex and actually quite easy to implement. Using an example, I showed what using a monad might look like, what the potential benefits of such an action are, and how it can differ from a normal method call.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION