雕刻模块化路径:protonmail Android应用程序的Lobzik工具案例研究
#kotlin #android #gradle #模块化

过早和逾期模块化问题

当您开始构建项目时,从一开始就可能会有很大的诱惑来使其模块化,以节省成本。但是,我认为过早的模块化不仅会耗尽您的资源,而且还会阻碍您项目的长期成功。在早期阶段,您的产品愿景可能不会完全形成,并且可能会发生重大变化。您最初建立的模块边界可能会随着项目通过众多迭代而发展并取得成功而迅速变得过时。

但是,随着您的团队的成长,功能的数量开始积累,模块化的技术债务可能会使您的脊椎发冷。您开始注意到GIT冲突和Heisenbugs的增加,原因是您的应用程序中缺乏明确的关注点。最终,您达到了声明“足够足够”的地步,并决定模块化您的整体。但是问题仍然存在:您在哪里开始弄清楚这种纠结的混乱?您不想阻止开发人员为您的项目抽出新功能,因此您需要查明最小的努力提取的最有影响力的领域。但是,如何在不花费大量时间来深入研究您的代码库中的空隙中实现这一目标?

介绍Lobzik:模块化工具包

负责使我的工作项目的代码库模块化,我凭借大量的200kloc巨石开始了一个寻求寻求的方法,以寻找推理这个chonky男孩的推理方法。作为图形的爱好者,我对整体内部的依赖关系网络感兴趣。很快,我发现该网络可以成为社区检测算法的好地方,该算法可以揭示看起来像模块的结构。经过数周的实验,我成功地设计了一种提取依赖图的方法,仔细选择了最合适的社区检测方法,并提出了产生最佳结果的技巧。

这些有见地的发现导致了我的宠物项目的诞生:Lobzik Gradle Plugin。您可以轻松地将我的工具集成到构建管道中,而不是依靠复杂的GUI图形工具包(例如Gephi或旋转装有NetworkX Python代码)的Jupyter笔记本。 Lobzik提供指导,指向您指导项目模块化的最佳途径。但是,此工具需要一些知识才能运行,因此,让本文作为您正确使用此工具的指南。

将lobzik应用于Protonmail Android应用

对于参考项目,我选择了ProtonMail Android App,它是尚未模块化的最大开源Android应用程序之一。主模块中有超过50kloc,它确实代表了一个值得模块化的整体。

cloc app/src/main --include-lang=Kotlin,Java

github.com/AlDanial/cloc v 1.96  T=0.39 s (2388.4 files/s, 249735.1 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Kotlin                         766           8867          16675          52105
Java                           156           2371           3381          13006
-------------------------------------------------------------------------------
SUM:                           922          11238          20056          65111
-------------------------------------------------------------------------------

设置Lobzik

要开始使用lobzik,我们需要在root build.gradle.kts文件中应用xyz.mishkun.lobzik插件:

plugins {
   // ...
   id("xyz.mishkun.lobzik") version "0.6.0"
}

然后,我们可以在同一build.gradle.kts文件中设置基本配置,如下所示:

lobzik {
    monolithModule.set(":app")
    packagePrefix.set("ch.protonmail.android")
    variantName.set("betaDebug")
}

在这里,我们设置了Monolith模块的名称(请注意“:”模块名称!),我们将要分析的变体名称和类的包装前缀。使用此配置,仅使用betaDebug变体检查:app模块内的ch.protonmail.android中的软件包中的代码。这对于我们的工具工作至关重要,因为我们不想处理所有库依赖关系和标准Kotlin库弄乱我们的依赖图。

第一次跑步

现在我们都设置了,我们可以使用命令第一次运行lobzik:

./gradlew lobzikReport

如果正确设置了所有内容,您将在项目root中找到build/reports/lobzik/analysis/report.html文件。现在让我们仔细研究如何解释该报告。

解释Lobzik报告

Lobzik报告由四个部分组成:

  • 核心候选人
  • Monolith模块表
  • 模块图
  • 整个图

整体模块表

吸引我们眼睛的第一件事是整体模块表。它列出了Lobzik检测到的所有模块。它们可以通过几个指标进行分类:coductance,cut and Monolithcut。

Monolith Modules Table screenshot

电导得分是该报告的这一部分的核心指标,因为它表明提取模块的益处比。较低的分数是可取的,得分为0,表明提取模块几乎不需要努力,因为它对其他模块没有依赖性。

剪切 Monolithcut 分数向我们展示了应破坏多少依赖项以成功提取模块。它有助于完善有关提取此模块需要多少精力的估计。

使用TF-IDF方法从其类自动生成模块的名称。单击模块名称将使我们进入“模块图”部分的详细报告。

模块图

Module Graphs section screenshot with module dependency graph and list of its classes

本节包含人均详细报告,每个报告介绍了三个小节:

  • 模块及其邻居的依赖图
  • 属于此模块的所有类的列表
  • 依赖依赖的列表需要破坏以提取此模块

整个图

该报告底部的本节表示模块依赖图,该图可以帮助识别由于对项目其余部分的依赖性较少而相对容易提取的模块。

“星”问题

仔细的读者可能会注意到我省略了报告的第一部分,称为核心候选人。本节在破坏者下倒塌,但在提高报告的有效性方面起着至关重要的作用。为了完全理解其价值,让我们探索我所说的“星”问题。

Graph of dependencies with many connections to the ListUtil.kt node

让我们考虑一个场景,其中我们有一个名为ListUtil.kt的类,该类别包含各种列表实用程序。该类在我们的代码库中大量使用,从而导致与网络中其他节点的许多连接。由于连接的高度,我们选择的社区检测算法是卢文因方法,可能会错误地将此类识别为大型社区的核心。重要的是要注意,社区检测算法最初是为社交网络设计的,在这种网络中,这些枢纽代表了一个由杰出个人领导的重要社区。

Same graph as above but with ListUtil.kt removed to reveal modularised structure

但是,对于代码库模块化问题,应将此类提取到核心模块。通过这样做,我们可以揭示代码其余部分的更好的模块化路径,如上图所示。为了帮助可视化提取此类课程的好处,Lobzik提供了ignoredClasses配置参数,该参数接受了应该从分析中排除的类名称的列表。

lobzik {
    // ...
    ignoredClasses.addAll("^ListUtils$")
}

但是,您可能会问我们如何识别此类课程?这些类通常是在负责依赖注入(DI)导航的部分中找到的,因为它们是连接其他松散耦合特征代码的胶水。忽略您的应用程序类和众所周知的公用事业类也是一个不错的选择。但是,如果我们已经消除了明显的类别,我们可以自动确定更多的核心类吗?这是报告的核心候选人部分变得有价值的地方。

核心候选人

Core Candidates section screenshot

核心候选人部分介绍了一张表,该表由基于学位或权威指标的班级前95%。彻底审查此列表可以帮助确定应排除在报告中的类。在质子邮件的情况下,可能会考虑以下类以消除:

lobzik {
    // ...
    ignoredClasses.addAll(
        ".*UserManager$",
        ".*Constants$",
        ".*ProtonMailApiManager$",
        ".*Util.*",
        ".*ProtonMailApplication$",
        ".*ResponseBody$",
        "Base.*",
        ".*Module",
        "^Message$",
        "^User$",
        "^ProtonMailApi$"
    )
}

通过从报告中消除这些类别,我们可以通过模块化得分来改善算法的性能,从0.586到0.684。一个很好的改进!现在,我们可以使用Lobzik报告开始提取一个检测到的24个模块。

结论

您可以在我的github上找到带有集成lobzik的Protonmail客户端的叉子。希望您喜欢使用lobzik对您的代码库进行模块化。我鼓励您尝试一下,不要犹豫地向项目的github

提交任何问题