框架与代码的形状
作为一个代码的设计者,我之前讨论过代码的形状,从“名字”出发,进行讨论。代码的形状:重构的方向-CSDN博客
从比喻的角度来看,名字似代码的血和肉,而框架则似代码的骨架。
猎豹和大象
在大自然中,每一种动物都有自己的骨架,因而有了自己的特性。
猎豹的骨架结构使其能够快速奔跑——它的脊柱高度灵活,四肢修长且肌肉集中,这种极致的优化让它成为陆地上速度最快的动物。然而,这种特化也带来了代价:猎豹的骨架无法支撑过重的身体,也无法适应复杂的地形。相比之下,大象的骨架结构则显得厚重而稳健——它的四肢粗壮如柱,脊柱坚固且略微拱起,这种设计让它能够承载数吨的重量,并适应各种复杂环境(如沼泽、丛林)。然而,大象的骨架也限制了它的灵活性,使其无法像猎豹一样快速奔跑或急转弯。
之所以用猎豹和大象来做比喻,也是因为它们也对应了两种典型的框架思路,“显式框架”和“隐式框架”。
典型的显式框架
,如React、Vue,就像猎豹的骨架一样,高度特化,专为速度而设计。借助这些框架,我们能够迅速搭建起项目,项目的完成周期甚至可以缩短至以周计。至于隐式框架
,我认为将其称为非显示框架
或许更为贴切,它更适用于处理那些具有不确定性的,酷似丛林探险特征的任务。
我也很幸运,最近同时在处理一个Vue3的项目和一个为vue3服务的cli。前者使用了上面讨论的显式框架
,后者则使用了隐式框架
。
显式框架-Vue3
这个Vue3项目是基于Geek-Admin来二次开发的。Vue 3 项目中典型的文件夹结构(如 hooks、views、components、routes)正是其显式框架
特征的直接体现。这种结构不仅反映了 Vue 3 的设计哲学,还展示了显式框架如何通过约定与约束塑造代码的形状。
一、文件夹结构的显式特征
-
components
:组件化约束- Vue 3 强制开发者将 UI 拆分为可复用的组件,每个组件是一个独立的单元(
.vue
文件)。 - 显式体现:组件必须放在
components
文件夹中,遵循单文件组件(SFC)规范。 - 示例:
/components /ECharts /index.vue /config /index.ts /ProTable /index.vue
- Vue 3 强制开发者将 UI 拆分为可复用的组件,每个组件是一个独立的单元(
-
views
:页面级抽象views
文件夹通常用于存放页面级组件,每个页面对应一个路由。- 显式体现:页面与路由的对应关系通过文件夹结构清晰表达。
- 示例:
/views /about.vue /assembly /batchImport /index.vue /index.scss
-
routes
:路由集中管理- Vue Router 的配置通常集中在
routes
文件夹中,显式定义路由与组件的映射关系。 - 显式体现:路由配置与页面逻辑分离,便于维护和扩展。
- 示例:
// /routes/index.js const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, ];
- Vue Router 的配置通常集中在
-
hooks
:逻辑复用抽象- Vue 3 的 Composition API 鼓励将可复用逻辑抽象为自定义 Hook(类似于 React 的 Hooks)。
- 显式体现:逻辑复用代码集中在
hooks
文件夹中,与 UI 组件分离。 - 示例:
// /hooks/usePagination.ts import { ref } from "vue"; import type { Pageable, DataLoader } from "@/components/interface"; export function usePagination( initialPageSize: number | null, // 初始化每一页的数量,如果为null,则表示不处理翻页 loadCallback: DataLoader | null // 翻页回调,如果设置为null,那么将不处理翻页回调 ) { ... return { pageable: _pageable, ... }; }
上面是基于vue框架呈现出来的代码形状,我们一般都用这样的文件结构去一步一步地搭建基于vue的项目。
为什么呢?因为它符合vue显式框架
的特征。
-
约定优于配置
显式框架通过文件夹命名和文件位置显式定义代码的职责(如 components 存放组件,views 存放页面,hooks 存放可复用的逻辑,stores 管理全局状态)。这种“约定优于配置”的设计,不仅降低了项目的维护门槛,还让开发者能够快速理解代码结构,从而提升团队协作效率和开发速度。 -
职责分离
显式框架通过文件夹结构实现职责分离(如 components 负责 UI,stores 管理状态,hooks 封装逻辑),使得不同开发者在维护同一项目时,能够自然地遵循高内聚、低耦合的设计原则。这种一致性不仅降低了沟通成本,还让代码更易维护和扩展,从而有效抵御代码腐烂,延长项目的生命周期。
显式框架
的特征还体现了高度的可预测性,基于显式框架
的约定,可以快速估算出需要开发的页面数量及组件数量,因而使得项目的开发周期更容易量化和评估——无论是新功能的添加,还是问题的排查,开发者都能快速定位相关代码,从而提升整体交付效率。
与显式框架
不同,隐式框架
则是另外一番景象。
隐式框架
首先隐式框架
并没有约定的文件目录结构,也就是说它没有事先的约定来指导代码的功能与代码形状之间的关系。例如,在上面讨论的显式框架
Vue中,公共的UI组件,放在components文件夹下,页面放在views文件夹下等。同样,在隐式框架
中,对于即将增加的新功能,应该将其安放在何处,也没有像显式框架
那么显而易见。
安放在何处也许就是隐式框架
本身的答案。
之前写了一篇文章代码设计的几个关注点,这也算是我在隐式框架
主题上的一次探索。在那篇文章中,我讨论了通过重构变量名和文件名来解决安放何处
的问题,同时也提到了“王阳明”先生的“心即理”。如果非要用一句话来概括,“逻辑上的合理性”吧。
在但后面的思考中,我发现很难用一句话,或者不应该用一句话来阐述隐式框架
,原因很简单,如果可以,那它就不是隐式框架
了。
像vue3-project-cli这样的项目,本身就是探索性的,没有明确的目标,就像在丛林中探险,猎奇,甚至还有一些碰运气的成分。
对于这个项目,开发语言使用的是Rust,命令行参数使用的是clap,typescript代码的处理使用的是swc,数据的反序列化处理使用的是serde。这3个库在Rust生态中的使用频率极高,具备了显性的特征。也就是说,在Rust的生态中,涉及读取命令行参数的相关功能,都会选择clap,涉及到typescript或者javascript的相关ast的处理,都会选择swc,涉及到序列化与反序列化,动态数据的读取,都会想到serde。
但如何组织这些功能的代码,却没有一个显式的约定。但没有显示的约定并不意味着没有章法,接下来进入命令式
和函数式
的话题。
命令式与函数式
在没有“显式框架”之前,命令式编程和函数式编程是隐藏在背后指导代码生长的基因,它们决定着代码的形状。
命令式关注如何实现具体的业务逻辑,在命令式编程中,变量用于存储数据值,这些值可以在程序执行过程中被修改和更新。它被用于系统编程,高性能应用领域,以及和硬件结合紧密的领域。在命令式的编程上可以看到汇编语言的影子,以实现为目的,以修改状态为目的。
命令式编程的代码的形状具有如下特点:
- 顺序执行:
- 命令式编程范式的代码通常按照顺序执行,即程序中的语句按照书写的顺序从上到下依次执行。
- 控制结构(如循环、条件语句)用于控制程序的执行流程,但整体上仍然遵循顺序执行的原则。
- 可变状态:
- 命令式编程范式允许程序中的变量在执行过程中被修改和更新。
- 程序的状态(如变量的值)是程序执行过程中重要的组成部分,状态的变化直接影响程序的执行结果。
- 显式控制:
- 开发者需要显式地指定程序的执行步骤和控制流程。
- 程序的执行过程需要开发者进行精细的控制和管理,包括内存管理、错误处理等。
函数式编程通过引入高阶函数和不可变数据结构等概念,提供了一种更加声明式、抽象化的数据处理方式。这种方式使得代码更加简洁、易读、可预测和易于并行化。函数式编程被大量的应用于数据处理,事件处理以及并发处理中。
函数式编程的代码形状具有如下特点:
- 函数式组合:
- 函数式编程范式的代码通常通过函数的组合和变换来实现复杂的逻辑。
- 高阶函数(如映射、过滤、归约等)用于抽象数据处理逻辑,使得代码更加简洁和易读。
- 不可变数据:
- 函数式编程范式强调数据的不可变性,即数据一旦创建就不可修改。
- 数据的更新通过创建新的数据副本并应用修改来实现,从而避免了共享状态和副作用。
- 声明式编程:
- 函数式编程范式倾向于声明式编程风格,即描述“做什么”而非“怎么做”。
- 开发者需要关注问题的解决方案而非具体的执行步骤,这使得代码更加接近数学表达式,易于理解和推理。
结束啦,也是从这里开始
我们应该有所察觉,即便在理想的状态下,我们的项目选择了命令式或者函数式编程范式,这仍然只是编程理念的一部分。在实际的开发过程中,我们还倾向于通过一个显式框架来提供约定和规范,让我们的代码更加有序、可维护。
回望过去,在Vue框架诞生之前,繁荣的是JQuery。JQuery以其简洁易用的特性,极大地简化了DOM操作和事件处理,成为了当时Web开发者的首选。然而,随着Web应用的日益复杂,隐式的、松散的约定已经无法满足开发的需求。
正是在这个背景下,隐式框架向显式框架的转换过程悄然展开。这一转换不仅带来了技术上的革新,更蕴藏着无数的机会。显式框架通过提供明确的约定和规范,使得代码更加易于理解和维护,降低了开发成本,提高了开发效率。同时,它也促进了开发者之间的协作,使得团队开发变得更加顺畅。
而即便我们已经有了Vue3这样的成熟且强大的显式框架,这个转换过程仍然没有结束。技术的进步是永无止境的,随着Web应用的不断发展和变化,我们对框架的需求也在不断变化。新的挑战和问题不断涌现,需要我们不断探索和创新。因此,隐式框架向显式框架的转换,或者说框架的不断演进和完善,是一个持续的过程。
也许这就是我继续在vue3-project-cli投入精力的勇气吧。