Read this article and learn how to use declarative code has the function of a combination of a better programmer.
In many cases, declarative solutions over traditional combination of features provides the imperative code of the code. Read this article and learn how to use declarative code has the function of a combination of a better programmer.
In this article, we will examine three examples of questions and study two different technologies (imperative and declarative) to solve these problems.
All source code in this article is open source, available from https://github.com/minborg/imperative-vs-declarative get. Finally, we will also see this article to learn how to program applies to database applications. We will use Speedment Stream as an ORM tool, because it provides the database tables, views, and connect the corresponding standard Java Streams, and declarative support structure.
Indeed there are numerous examples may code for candidate metric evaluation.
1. Example of the Problem
In this article, I chose three common problems developers may encounter in their daily work:
1.1.SumArray
Iterative array and perform calculations
1.2.GroupingBy
Parallel aggregate value
1.3.Rest
Use paging implement REST interface
2. Solutions Technology
As described in the beginning of this article, we will use both coding technology to solve the problem:
2.1 Command solutions
A command-based solution, we use the traditional code samples for loops and with explicit state variable.
2.2 declarative solutions
Declarative solution where we have a combination of various functions to form the composite higher-order function to solve the problem, usually java.util.stream.Stream
, or a variant thereof.
3. Code Index
However, our idea is to use SonarQube (here SonarQube Community Edition, Version 7.7) will be applied to different static code analysis solution, so that we can be a problem / solution portfolio to derive useful and standardized code metrics. Then compare these indicators.
In this article, we will use the following code metrics:
3.1. PLACE
"LOC" means "line" is the number of non-null line of code.
3.2. Statements
The total number of statements in the code. You may have zero to more than one statement on each line of code.
3.3. Complexity cycle
It represents the complexity of the code, and is quantified by the number of linearly independent paths metrics source code. For example, a single "if" clause shows two separate paths in the code. Read more on Wikipedia content .
3.4. Cognitive complexity
SonarCube stated:
"Cognitive complexity has changed the practice of using mathematical models to assess the maintainability of the software. It starts Cyclomatic Complexity set a precedent, but use human judgment to assess how the structure should be calculated, and decide what should be added to the model as a whole result, it produces a fraction of the complexity of the method, which allows the programmer to evaluate the maintainability model of more equitable than before. "
On SonarCube own page you can read more content .
Under normal circumstances, we need to imagine a solution where these indicators is very small and not very big.
For the record, it should be noted that following the design of any solution is just a way to solve any given problem. If you know a better solution, please feel free to https://github.com/minborg/imperative-vs-declarative pulling request to submit comments.
4. iterative array
Let's start simple. Object of this calculation example of the problem is the sum of the elements of the array int, and returns the result to a long. The following interface defines the problem:
public interface SumArray {
long sum(int[] arr);
}
4.1. Imperative Solutions
The following solution uses technology SumArray imperative question:
public class SumArrayImperative implements SumArray {
@Override
public long sum(int[] arr) {
long sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
}
4.2 declarative solutions
This is a declarative technology SumArray solution:
public class SumArrayDeclarative implements SumArray {
@Override
public long sum(int[] arr) {
return IntStream.of(arr)
.mapToLong(i -> i)
.sum();
}
}
Please note, IntStream :: sum returns only one int, therefore, we must join the intermediate operating mapToLong ().
4.3 Analysis
SonarQube provides the following analysis:
SumArray code metric table below (generally lower):
technology | PLACE | Statements | The complexity of the cycle | Cognitive complexity |
---|---|---|---|---|
Imperative | 12 | 5 | 2 | 1 |
Functional | 11 | 2 | 2 | 0 |
This is its value (generally lower) in the diagram:
5. Parallel aggregate value
The object of this example is to issue Person objects grouped into different buckets, where each bucket constitute a unique combination of a person's year of birth and the country of a person's work. For each group, the average salary should be calculated. The polymerization should be in the public pool ForkJoin parallel computing.
This is the (immutable) the Person class:
public final class Person {
private final String firstName;
private final String lastName;
private final int birthYear;
private final String country;
private final double salary;
public Person(String firstName,
String lastName,
int birthYear,
String country,
double salary) {
this.firstName = requireNonNull(firstName);
this.lastName = requireNonNull(lastName);
this.birthYear = birthYear;
this.country = requireNonNull(country);
this.salary = salary;
}
public String firstName() { return firstName; }
public String lastName() { return lastName; }
public int birthYear() { return birthYear; }
public String country() { return country; }
public double salary() { return salary; }
// equals, hashCode and toString not shown for brevity
}
We also define another class called YearCountry immutable, and the group key as:
public final class YearCountry {
private final int birthYear;
private final String country;
public YearCountry(Person person) {
this.birthYear = person.birthYear();
this.country = person.country();
}
public int birthYear() { return birthYear; }
public String country() { return country; }
// equals, hashCode and toString not shown for brevity
}
After these two classes defined, we can now define an example of this problem through the interface:
public interface GroupingBy {
Map<YearCountry, Double> average(Collection<Person> persons);
}
5.1. Imperative solution
Order to achieve GroupingBy example problem solutions will not be easy. This is a solution to the problem:
public class GroupingByImperative implements GroupingBy {
@Override
public Map<YearCountry, Double> average(Collection<Person> persons) {
final List<Person> personList = new ArrayList<>(persons);
final int threads = ForkJoinPool.commonPool().getParallelism();
final int step = personList.size() / threads;
// Divide the work into smaller work items
final List<List<Person>> subLists = new ArrayList<>();
for (int i = 0; i < threads - 1; i++) {
subLists.add(personList.subList(i * step, (i + 1) * step));
}
subLists.add(personList.subList((threads - 1) * step, personList.size()));
final ConcurrentMap<YearCountry, AverageAccumulator> accumulators = new ConcurrentHashMap<>();
// Submit the work items to the common ForkJoinPool
final List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int i = 0; i < threads; i++) {
final List<Person> subList = subLists.get(i);
futures.add(CompletableFuture.runAsync(() -> average(subList, accumulators)));
}
// Wait for completion
for (int i = 0; i < threads; i++) {
futures.get(i).join();
}
// Construct the result
final Map<YearCountry, Double> result = new HashMap<>();
accumulators.forEach((k, v) -> result.put(k, v.average()));
return result;
}
private void average(List<Person> subList, ConcurrentMap<YearCountry, AverageAccumulator> accumulators) {
for (Person person : subList) {
final YearCountry bc = new YearCountry(person);
accumulators.computeIfAbsent(bc, unused -> new AverageAccumulator())
.add(person.salary());
}
}
private final class AverageAccumulator {
int count;
double sum;
synchronized void add(double term) {
count++;
sum += term;
}
double average() {
return sum / count;
}
}
}
5.2. Declarative Solution
This is a declarative structure to achieve GroupingBy solution:
public class GroupingByDeclarative implements GroupingBy {
@Override
public Map<YearCountry, Double> average(Collection<Person> persons) {
return persons.parallelStream()
.collect(
groupingBy(YearCountry::new, averagingDouble(Person::salary))
);
}
}
In the above code, I used some static imports from Collectors class (for example Collectors :: groupingBy). This does not affect the code index.
5.3 Analysis
SonarQube provides the following analysis:
GroupingBy
Code metric table below (generally lower):
technology | PLACE | Statements | The complexity of the cycle | Cognitive complexity |
---|---|---|---|---|
Imperative | 52 | 27 | 11 | 4 |
Functional | 17 | 1 | 1 | 0 |
This is its value (generally lower) in the diagram:
6. Implement a REST interface
In this example problem, we will provide paging services Person object. Persons appear on the page must satisfy certain (optional) condition, press a specific order. This page will be returned as unmodifiable list of Person objects.
This is a problem of the interface:
public interface Rest {
/**
* Returns an unmodifiable list from the given parameters.
*
* @param persons as the raw input list
* @param predicate to select which elements to include
* @param order in which to present persons
* @param page to show. 0 is the first page
* @return an unmodifiable list from the given parameters
*/
List<Person> page(List<Person> persons,
Predicate<Person> predicate,
Comparator<Person> order,
int page);
}
The size of the page in a separate utility class named RestUtil in:
public final class RestUtil {
private RestUtil() {}
public static final int PAGE_SIZE = 50;
}
6.1. Imperative Implementation
public final class RestImperative implements Rest {
@Override
public List<Person> page(List<Person> persons,
Predicate<Person> predicate,
Comparator<Person> order,
int page) {
final List<Person> list = new ArrayList<>();
for (Person person:persons) {
if (predicate.test(person)) {
list.add(person);
}
}
list.sort(order);
final int from = RestUtil.PAGE_SIZE * page;
if (list.size() <= from) {
return Collections.emptyList();
}
return unmodifiableList(list.subList(from, Math.min(list.size(), from + RestUtil.PAGE_SIZE)));
}
}
6.2. Declarative Solution
public final class RestDeclarative implements Rest {
@Override
public List<Person> page(List<Person> persons,
Predicate<Person> predicate,
Comparator<Person> order,
int page) {
return persons.stream()
.filter(predicate)
.sorted(order)
.skip(RestUtil.PAGE_SIZE * (long) page)
.limit(RestUtil.PAGE_SIZE)
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
}
}
6.3 Analysis
SonarQube provides the following analysis:
Rest
Code metric table below (generally lower):
technology | PLACE | Statements | The complexity of the cycle | Cognitive complexity |
---|---|---|---|---|
Imperative | 27 | 10 | 4 | 4 |
Functional | 21 | 1 | 1 | 0 |
This is its value (generally lower) in the diagram:
7.Java 11 improvements
The example above is written in Java 8. Use Java 11, we can use LVTI (local variable type inference) to shorten the declarative code. This makes our code shorter, but will not affect the code index.
@Override
public List<Person> page(List<Person> persons,
Predicate<Person> predicate,
Comparator<Person> order,
int page) {
final var list = new ArrayList<Person>();
...
Compared with Java 8, Java 11 contains a number of new collectors. For example, Collectors.toUnmodifiableList
(), it will make our solutions more declarative Rest shorter:
public final class RestDeclarative implements Rest {
@Override
public List<Person> page(List<Person> persons,
Predicate<Person> predicate,
Comparator<Person> order,
int page) {
return persons.stream()
.filter(predicate)
.sorted(order)
.skip(RestUtil.PAGE_SIZE * (long) page)
.limit(RestUtil.PAGE_SIZE)
.collect(toUnmodifiableList());
}
Again, this will not affect the code index.
8. Summary
Code three exemplary average measurement result in the following problems (generally lower):
Given the input requirements herein, when we constructed from imperative to declarative configuration, all code metrics are significantly improved.
8.1. Declarative configuration in a database application
In order to obtain the benefits of declarative structure of the database application, we use Speedment Stream . Speedment Stream is a stream-based Java ORM tool can be any database table / view / connection to Java stream, allowing you to use declarative skills in database applications.
Your database application code will become better. In fact, for Speedment database REST and Spring Boot paging solution may be expressed as follows:
public Stream<Person> page(Predicate<Person> predicate,
Comparator<Person> order,
int page) {
return persons.stream()
.filter(predicate)
.sorted(order)
.skip(RestUtil.PAGE_SIZE * (long) page)
.limit(RestUtil.PAGE_SIZE);
}
Manager<Person> persons
By Speedment, and constitute a "Person" handle database tables, you can use @AutoWired annotation by Spring.
9. summary
Select the command declarative solutions can greatly reduce the general code complexity, and can provide many benefits, including faster encoding, better code quality, more readable, less testing, lower maintenance costs and many more.
In order to benefit from the declarative configuration database applications in, Speedment Stream is a standard Java Streams can provide tools directly from the database.
Master declarative structure and function of a combination of any of today's modern Java developer required.
August welfare struck on time, public concern number backstage Re: July 003 can receive a translation of highlights oh go on welfare Re: 001, 002, you can receive!