如何成为更好的程序员?

阅读本文并了解如何使用具有功能组合的声明性代码成为更好的程序员。

在许多情况下,具有功能组合的声明性解决方案提供优于传统命令式代码的代码度。阅读本文并了解如何使用具有功能组合的声明性代码成为更好的程序员。

在本文中,我们将仔细研究三个问题示例,并研究两种不同的技术(命令式和声明性)来解决这些问题。

本文中的所有源代码都是开源的,可从https://github.com/minborg/imperative-vs-declarative获取。最后,我们还将看到本文的学习如何应用于数据库应用程序领域。我们将使用Speedment Stream作为ORM工具,因为它提供了与数据库中的表,视图和连接相对应的标准Java Streams,并支持声明性构造。

实际上有无数个候选示例可用于代码度量评估。

1.问题示例

在本文中,我选择了开发人员在日常工作可能遇到的三个常见问题:

1.1.SumArray

迭代数组并执行计算

1.2.GroupingBy

并行聚合值

1.3.Rest

使用分页实现REST接口

2.解决方案技术

正如本文开头所描述的,我们将使用这两种编码技术解决问题:

2.1 命令式解决方案

一个命令式的解决方案,我们使用带有for循环和显式可变状态的传统代码样例。

2.2 声明式解决方案

声明式解决方案,其中我们组合各种函数以形成解决问题的高阶复合函数,通常使用java.util.stream.Stream或其变体。

3.代码指标

然而,我们的想法是使用SonarQube(此处为SonarQube Community Edition,Version 7.7)将静态代码分析应用于不同的解决方案,以便我们可以为问题/解决方案组合推导出有用且标准化的代码度量标准。然后将比较这些指标。

在本文中,我们将使用以下代码度量标准:

3.1. LOC

“LOC”表示“代码行”,是代码中非空行的数量。

3.2. Statements

是代码中的语句总数。每个代码行上可能有零到多个语句。

3.3. 循环复杂性

表示代码的复杂性,并且是通过源代码程序的线性独立路径数量的定量度量。例如,单个“if”子句在代码中显示两条单独的路径。在维基百科上阅读更多内容

3.4。认知复杂性

SonarCube声称:

“认知复杂性改变了使用数学模型来评估软件可维护性的实践。它从Cyclomatic Complexity设定的先例开始,但是使用人为判断来评估结构应该如何计算,并决定应该将什么添加到模型中作为一个整体结果,它产生了方法复杂性分数,这使得程序员对可维护性模型的评估比以前更公平。“

在SonarCube自己的页面上可以阅读更多内容

通常情况下,需要设想一个解决方案,其中这些指标很小而不是很大。

对于记录,应该注意下面设计的任何解决方案只是解决任何给定问题的一种方法。如果您知道更好的解决方案,请随时通过https://github.com/minborg/imperative-vs-declarative拉取请求提交意见。

4.迭代数组

我们从简单开始。此问题示例的对象是计算int数组中元素的总和,并将结果返回为long。以下接口定义了问题:

public interface SumArray {
    long sum(int[] arr);
}

4.1.命令式解决方案

以下解决方案使用命令式技术实现SumArray问题:

public class SumArrayImperative implements SumArray {
    @Override
    public long sum(int[] arr) {
        long sum = 0;
        for (int i : arr) {
            sum += i;
        }
        return sum;
    }
}

4.2声明式解决方案

这是一个使用声明性技术实现SumArray的解决方案:

public class SumArrayDeclarative implements SumArray {
    @Override
    public long sum(int[] arr) {
        return IntStream.of(arr)
            .mapToLong(i -> i)
            .sum();
    }
}

请注意,IntStream :: sum只返回一个int,因此,我们必须加入中间操作mapToLong()。

4.3.分析

SonarQube提供以下分析:

SumArray的代码度量标准如下表所示(通常更低):

技术 LOC Statements 循环复杂性 认知复杂性
Imperative 12 5 2 1
Functional 11 2 2 0

这是它在图表中的值(通常更低):

5.并行聚合值

这个问题示例的对象是将Person对象分组到不同的桶中,其中每个桶构成一个人的出生年份和一个人工作的国家的唯一组合。对于每个组,应计算平均工资。聚合应使用公共ForkJoin池并行计算。

这是(不可变的)Person类:

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
}

我们还定义了另一个名为YearCountry的不可变类,把它作为分组键:

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
}

定义了这两个类之后,我们现在可以通过接口定义此问题示例:

public interface GroupingBy {
    Map<YearCountry, Double> average(Collection<Person> persons);
}

5.1.命令式的解决方案

实现GroupingBy示例问题的命令式解决方案并非易事。这是问题的一个解决方案:

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. 声明式解决方案

这是一个使用声明性构造实现GroupingBy的解决方案:

public class GroupingByDeclarative implements GroupingBy {
    @Override
    public Map<YearCountry, Double> average(Collection<Person> persons) {
        return persons.parallelStream()
            .collect(
             groupingBy(YearCountry::new, averagingDouble(Person::salary))
            );
    }
}

在上面的代码中,我使用了一些来自Collectors类的静态导入(例如Collectors :: groupingBy)。这不会影响代码指标。

5.3.分析

SonarQube提供以下分析:

GroupingBy的代码度量标准如下表所示(通常更低):

技术 LOC Statements 循环复杂性 认知复杂性
Imperative 52 27 11 4
Functional 17 1 1 0

这是它在图表中的值(通常更低):

6.实现REST接口

在该示例性问题中,我们将为Person对象提供分页服务。出现在页面上的Persons必须满足某些(任意)条件,并按特定顺序排序。该页面将作为不可修改的Person对象列表返回。

这是一个解决问题的接口:

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);
}

页面的大小在名为RestUtil的单独工具程序类中:

public final class RestUtil {
    private RestUtil() {}
    public static final int PAGE_SIZE = 50;
}

6.1.命令式实现方法

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.声明式解决方法

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.分析

SonarQube提供以下分析:

Rest的代码度量标准如下表所示(通常更低):

技术 LOC Statements 循环复杂性 认知复杂性
Imperative 27 10 4 4
Functional 21 1 1 0

这是它在图表中的值(通常更低):

7.Java 11改进

上面的例子是用Java 8编写的。使用Java 11,我们可以使用LVTI(局部变量类型推断)缩短声明性代码。这会使我们的代码更短,但不会影响代码指标。

@Override
public List<Person> page(List<Person> persons,
                         Predicate<Person> predicate,
                         Comparator<Person> order,
                         int page) {
    final var list = new ArrayList<Person>();
    ...

与Java 8相比,Java 11包含一些新的收集器。例如,Collectors.toUnmodifiableList(),它将使我们的声明性Rest解决方案更短:

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());
}

同样,这不会影响代码指标。

8.摘要

三个示例性问题的平均代码度量产生以下结果(通常更低):

鉴于本文中的输入要求,当我们从命令式构造到声明式构造时,所有代码度量标准都有显着改进。

8.1.在数据库应用程序中使用声明性构造

为了在数据库应用程序中获得声明性构造的好处,我们使用了Speedment Stream。 Speedment Stream是一个基于流的Java ORM工具,可以将任何数据库表/视图/连接转换为Java流,从而允许您在数据库应用程序中应用声明性技能。

您的数据库应用程序代码将变得更好。事实上,针对数据库的Speedment和Spring Boot的分页REST解决方案可能表达如下:

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由Speedment提供,并构成数据库表“Person”的句柄,可以通过Spring使用@AutoWired注解。

9.总结

选择声明性命令式解决方案可以大大降低一般代码复杂性,并且可以提供许多好处,包括更快的编码,更好的代码质量,更高的可读性,更少的测试,更低的维护成本等等。

为了从数据库应用程序中的声明性构造中受益,Speedment Stream是一种可以直接从数据库提供标准Java Streams的工具。

掌握声明性构造和功能组合是当今任何当代Java开发人员必须的。



8月福利准时来袭,关注公众号
?
后台回复:003即可领取7月翻译集锦哦~
?
往期福利回复:001,002即可领取!

原文地址:https://www.cnblogs.com/liululee/p/11444588.html

时间: 2024-08-01 02:37:27

如何成为更好的程序员?的相关文章

七个方法成为更好的程序员!转收藏下来

1. 在怪罪其他东西之前先检查自己的代码 质疑一下你自己和他人的预设情况.来自不同供应商的工具,可能内置有不同的预设,也有可能相同的供应商提供不同的工具. 当有人想你报告一个你无法重复的问题之时,去看看他们做了些什么.他们可能会做一些你没有想到的事情,或者是按照不同的顺序来做那件事. 我的原则是,如果我遇到一个我无法避免的 bug 时,我会首先考虑是编译器的错误,然后我就会去检查堆栈是否被破坏了.这可以通过跟踪代码来实现,可以有效地移除问题.多线程问题是另一个绞尽脑汁也不容易找到的错误来源,通常

迈向更好的程序员的行列

学习最好的方式,是有个好师傅.他根据你的不同阶段,教导你不同的技能,循序渐进:师傅不单教你练功,还会教你做人,使你内修于心,外化于形.教你的一些道理,你可能当时不太懂,但等你苦练多日,历经曲折,终有一日茅塞顿开,再去学艺做事,事半功倍,大有精进: 有一个位好导师自然是得之我幸的事情,但实际工作中很难得,也许有前辈们偶尔的点拨,有朋友的激励,但最平实可靠的方法还是来自于阅读. 面对这份职业,你应如何开始? 和程序员读的绝大多数应用书籍不同的是,开篇给你讲的就是态度.对待错误.不整洁的代码.团队协作

十二星座哪个更适合做程序员?

原文链接 程序猿是一种常年处在被黑-自黑状态中的生物,他们的大部分时候都贡献给了他们热爱的代码事业,虽然大家都是程序员,但即使是同一种语言,每个程序员各自写起代码来还是有很多的不一样的,这或许和他们的星座和性格有很大的关系~ 处女座向来是被大家黑的比较惨的星座,但是如果是写代码,"完(jiao)美(zhen)"的处女座却变成了无数大公司欢迎的CTO种子级别选手!真是十年被黑一朝翻身! 说到白羊座,怎么可能看到一整段白羊座程序员写的完整代码!他们的电脑里大概存了三万多个文档,都是极其美妙

10步让你成为更优秀的程序员

1. 永远不要复制代码 不惜任何代价避免重复的代码.如果一个常用的代码片段出现在了程序中的几个不同地方,重构它,把它放到一个自己的函数里.重复的代码会导致你的同事 在读你的代码时产生困惑.而重复的代码如果在一个地方修改,在另外一个地方忘记修改,就会产生到处是bug,它还会使你的代码体积变得臃肿.现代的编程语 言提供了很好的方法来解决这些问题,例如,下面这个问题在以前很难解决,而如今使用lambdas却很好实现: /// /// 一些函数含有部分重复代码 /// void OriginalA() 

十步让你成为更优秀的程序员

1. 永远不要复制代码 不惜任何代价避免重复的代码.如果一个常用的代码片段出现在了程序中的几个不同地方,重构它,把它放到一个自己的函数里.重复的代码会导致你的同事在读你的代码时产生困惑.而重复的代码如果在一个地方修改,在另外一个地方忘记修改,就会产生到处是bug,它还会使你的代码体积变得臃肿.现代的编程语言提供了很好的方法来解决这些问题,例如,下面这个问题在以前很难解决,而如今使用lambdas却很好实现: /// <summary>   /// 一些函数含有部分重复代码   /// <

菜鸟好文推荐(二十三)——成为一名更好的程序员:如何阅读源代码

阅读源代码有许多益处.你会发现新的架构(construct)和库,与其他的代码维护者产生共鸣,但最重要的是学会如何组织代码,避免因内部极其复杂而变得不可维护. 但是也有一个不好的地方,那就是阅读源代码太困难了.每当我看到一个新的代码库(code base)时,这种让人眩晕的感觉就充斥了我的大脑.我的内心告诉我压根不想趟眼前这趟浑水. 这是(希望是)正常的反应.当我们的大脑接触过多的新东西,就会产生排斥.造物主赋予我们的这台强大的模式匹配机器根本找不到规律.所有的抽象(abstraction)都是

成为更好程序员的8种途径

是时候开始认真考虑一下如何升级你的开发技术了.让我们来认真地学习一下吧. 给自己设定一个提高开发技术的目标很容易,但是"想成为一名伟大的程序员"却不是一个容易实现的目标.首先,说"我想变得更好",是简历在你认识到"更好"的样子基础之上.另外,有太多的人追求进步而不知道如何去实现. 因此,让我分享八个可实际操作的知道方针,你可以把他们作为提高变成技能的流程图.这些指挥都是伴随着计算机35年的发展沉淀下来的. 1.时刻提醒自己:学习         

可以使你成为更优秀程序员的5个好习惯

我们都希望能够在我们所做的事情中得到成长,在WEB开发领域,我们花费时间最多的就是编写代码.这可能包括HTML, CSS, JavaScript, PHP, Python, ActionScript或者任何其他你构建WEB站点时选用的语言. 这篇文章中,我们将分享一些实际的步骤,使你可以扩充技能,成为一个更优秀的程序员.我们提出五个不同的生活习惯,它们可以使你在你所从事的领域变得更加优秀. 1. 一个时间段内只专注于一种类型的语言 如果你正在设计或开发网站,你将需要同时熟悉多种不同的语言.你可能

StackOverflow程序员推荐:每个程序员都应读的30本书

“如果能时光倒流,回到过去,作为一个开发人员,你可以告诉自己在职业生涯初期应该读一本,你会选择哪本书呢?我希望这个书单列表内容丰富,可以涵盖很多东西.” 很多程序员响应,他们在推荐时也写下自己的评语.以前就有国内网友介绍这个程序员书单,不过都是推荐数 Top 10的书.其实除了前10本之外,推荐数前30左右的书籍都算经典,伯乐在线整理编译这个问答贴,同时摘译部分推荐人的评语.下面就按照各本书的推荐数排列. 1. <代码大全>史蒂夫·迈克康奈尔 推荐数:1684 “优秀的编程实践的百科全书,&l