Web 开发要知道的三个D #1:声明式 VS 命令式

2次阅读

共计 5520 个字符,预计需要花费 14 分钟才能阅读完成。

本文是《Web 开发要理解的三个 D》中的第一个 D,Declarative(声明式) vs. Imperative(命令式)。
使用 JavaScript 进行的现代 web 开发在过去十年中不断发展,采用了通过各种库和框架实现的模式和良好实践。虽然很容易被 AngularJS, React, Vue 或 使用 MVVM(Model-View-ViewModel) 模式实现的 KendoUI 这种的框架所吸引,但要记住一点是使开发更容易的基本模式和可重复的实践是很重要的。事实上,在不了解“为何”的情况下,很难对开发堆栈做出有条件的决策。
自从上世纪 90 年代中期“万维网”上第一个商业应用程序开始出现以来,作者一直在编写程序或领导团队编写商业网站系列程序。在作者看来,有三个基本概念真正彻底改变了 web 应用程序的开发方式——作者称之为“web 开发的 3D”。

Declarative(声明式 vs 命令式)

Data-binding (数据绑定)

Dependency injection (依赖注入)

在使用 XAML 和 c# 构建 Silverlight 应用程序时,我了解了声明式代码和命令式代码之间的区别。很高兴看到这些概念被移植 到 HTML 和 JavaScript 开发中。这些概念有利于管理数据绑定。最后,依赖注入对于管理大型代码库 (或者由大型团队管理的中等大小的代码库) 至关重要,许多解决方案已经在 JavaScript 中解决了这个问题。
在这个简短的系列中,我将总结这些概念,并分享从案例研究中获得的经验教训,这些经验教训说明了它们为何如此重要。请注意,它们不是唯一的概念,但是如果你掌握了它们,我保证你会用不同的眼光重新看待你的项目、工具、库和框架。
本文将探讨的第一个概念:声明式与命令式。

声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。
命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。

举一个数据过滤的例子来说明这一点,比如我们要打印下数组中存不存在 3。
// 命令式编程做法
let res = false;
for(i = 0; i < dataArr.length; i++) {
if (i === 3) {
res = true;
}
}
console.log(res);

// 声明式编程做法
let res = dataArr.filter(i => i === 3);
console.log(res);

案例研究: 情感分析项目
在详细介绍我所说的声明式方法和命令式方法之前,让我先分享一个来自我个人经验的例子,我认为这个例子说明了为什么对这些方法的深入理解是非常重要的。虽然这个例子是使用 Silverlight 和 c# 构建的,但是同样的方法也可以应用到 JavaScript 应用程序开发中。
2009 年底,微软联系了我的公司,帮助他们重写他们正在开发的内部 Silverlight 应用程序。这是一个非常有效的“臭鼬工程”项目之一,但是需要进行重构,以便为黄金时间做好准备。
作为一个没有设计背景开发人员来,我知道我需要设计公司的服务,并协同工作来创建下一代解决方案。该应用程序在当时处于领先地位,因为它聚合了来自各种社交网络平台的数据,然后运行情绪分析这个项目,以确定帖子是“正面”还是“负面”,最终使竞选活动的管理能够处理负面情绪。
关于如何使用此应用程序的一个很好的例子是,一家汽车制造商发布了大规模的召回。该引擎将收集与召回相关的数据,并生成一个典型的负面基线情绪。然后,该公司将发起一项活动,对消费者进行指导,并向消费者道歉和赔偿,然后衡量对消费者情绪的总体影响。现在有很多平台和引擎可以进行这类分析和活动。
这个项目的关键是时间紧迫,所以设计和开发需要并行进行。一个更传统的方法是经历一个设计阶段,提供线框图和各种设计组合,直到团队就经验的具体实现达成一致,然后开始开发。我们没有时间遵循那种方法。
幸运的是,我们没有这样做。
我在应用程序中记得的一个简单例子是允许用户选择自己喜欢的活动。在原始界面中,用户从下拉列表中选择一个活动,然后这个活动详细信息出现在屏幕上的选择区域。UI 最终设计在左窗格中显示每个活动的详细信息,当单击活动“平铺”时,右窗格中的相同细节会亮起。谢天谢地,这需要最小的重构,因为 XAML 使用了声明式的方法,这样团队就能够改变一些简单的标记,而不需要接触一行代码。
声明式和命令式定义
使用 XAML 和 C# 或 HTML 和 JavaScript 来定义命令式和声明式编程非常容易。代码 (c# 或 JavaScript) 是必需的。标签 (无论是 XAML 还是 HTML) 是声明性的。
为了进一步说明,你可以使用命令式代码描述如何执行你想要执行的操作。相反,声明性代码使你能够描述想要做什么,而不用担心如何做。
例如,在使用 jQuery 的 Kendo UI 中,你可以编写以下代码来定义一个文本框,该文本框接受数值,并且不允许字母数字输入或超出定义范围的输入。
$(“#textbox”).kendoNumericTextBox({
value: 10,
min: -10,
max: 100,
step: 0.75,
format: “n”,
decimals: 3
});

在该代码中,jQuery 被指示找到一个 id 为 textbox 的 DOM 元 素,然后调用扩展方法 kendoNumericTextBox,并传入配置初始值、范围等的各种参数。这就是如何使用伪命令式的方法将一个简单的 DOM 元素转换为 Kendo UI 的小部件。
如果你在 Angular 中使用 Kendo UI,你可能会这样做
<input kendo-numerictextbox k-min=”-10″ k-max=”100″ k-step=”0.75″
k-format=”n” k-decimals=”3″/>

该示例采用声明式方法。你只需声明要发生的事情,而不是担心如何转换元素,这应该是一个数字文本框; 它的最小值应为 -10,等等。
这是一个非常重要的区别。在情绪分析应用程序中,如果团队强制定位下拉列表然后使用列表填充它,则使用一组切片的更改将需要更改 UI(XAML)和代码(C#)。相反,团队只是使用 C# 来构建和公开广告系列列表并跟踪选定的广告系列。下拉列表中的 XAML 声明了它将使用的一系列广告系列。当它被重构为列表时,XAML 被更新为 ListBox 小部件,该小部件声明了相同的营销活动数组。
在使用 jQuery 的 JavaScript 中,命令式方法如下所示:
var options = $(“#options”);
$.each(result, function() {
options.append($(“<option />”).val(this.id).text(this.name));
});

重构为循环并构造 div 标签列表以及绑定到 click 事件。
使用 Angular 的命令式方法,原始标签:
<select ng-options=”item.name for item in items track by item.id”>
</select>

可以重构为:
这样可以在设计中提供更大的灵活性,而无需重新原代码,这样程序员可以在设计更新时转移到其他任务上。
设计人员 - 开发人员工作流
你可能参与了利用顺序工作流程的项目。在设计完成之前,开发基本上被搁置,然后开发人员参与实施设计。声明式和命令式代码的清晰定义和分离可实现更加并行的工作流程。这是因为即使数据没有,UI 的实现也会发生变化。
最后,要做的大部分事情是将数据转换为信息。数据本身作为位和字节对用户来说不是很有用,但是,作为图形或图块或输入框呈现,它变得有意义。
我想把这个过程进一步分解成三个不同的部分。

最终驱动大多数业务应用程序的是数据,
但用户界面使其有意义,
并应用某些行为来增强体验将这三者分开可确保应用程序的最大灵活性和可维护性。我发现即使接口还没有设计,几乎总是可以就特定接口需要什么数据达成一致。

案例研究: 电视节目列表
一个完美的例子是,我们的团队与有线电视公司合作,为他们的频道列表构建一个新的体验。我们甚至在他们完成检索信息的 api 之前就开始了这个项目! 幸运的是,有许多关于清单的属性是相当通用的。

电视频道通常具有与之关联的频道数,编码和描述
列表有一个类别(恐怖、纪录片、喜剧等)
列表有标题
列表可能有与之相关联的参与者

我们使用命令式代码来定义数据结构以保存信息,检索信息(最初通过一组模拟或虚拟接口,稍后重构以便在开发后与实时 API 连接),并公开信息。这种开发没有任何依赖于完整的 API 或任何级别的设计。
在设计团队迭代线框图或对比图时,团队甚至为清单设计了一个简单的网格,以便与之交互。最终设计了网格,并更新了声明性标签以实现设计效果。不需要修改代码,到那时所有的东西都经过了完整的测试。
此外,还定义了一组行为。当用户单击网格中的列表时,应用初始行为以弹出包含列表信息的对话框。之后,设计团队决定让网格在远离观察者的 3D 轴上摆动并从边缘滑入对话框会更具视觉吸引力。这有助于用户在网格中保留其上下文,但为列表启用了更多的空间。
对话框和网格都是在声明式标签中定义的。弹出窗口是作为一种可以声明式应用的行为编写的。以 HTML 为例,它可能看起来像这样
<div class=”listingTile” popupTarget=”dialog”></div>

slide-in 效果控制父网格和对话框,因此写入的行为是这样实现的:
<div class=”listingTile” slideParent=”grid” slideTarget=”dialog”></div>

编写命令式代码来实现该行为。一旦编写,它就以声明方式应用于使用该行为的任何元素。
行为
你可能听说过面向切面编程(AOP)这个术语。它旨在将某些共同关注点或行为与代码分开。可以在不修改代码的情况下添加不是你正在处理的命令逻辑核心的行为,例如日志记录或身份验证。这是怎么做到的?
你可能一直在使用面向切面的代码,甚至不知道它。考虑以下代码片段
public Widget GetWidget()
{
if (HttpContext.Current.User.IsInRole(“Widgeteer”))
{
return Widget();
}
}

在该示例中,命令式代码用于授权身份验证的用户检索信息。授权是一个跨领域的问题,这意味着它可以在整个应用程序中使用,而不是针对单个功能。
解决授权的一种更常见的方法是使用行为来检查角色,而无需修改代码本身。你可能写过这样的东西:
[Authorize(Roles=”Widgeteer”)]
public Widget GetWidget()
{
return Widget();
}

在第二个示例中,方法本身关注返回 Widget 的特定任务。通过对方法应用授权行为来处理授权的交叉问题。这是使用属性完成的。
它起初可能看起来不那么令人印象深刻,但现在你可以告诉你的老板你是一个面向切面的程序员,并使用声明式代码来解决你的必要业务逻辑的交叉问题。
TypeScript 和 ES7 装饰器
ECMAScript7 (又名 JavaScript 2016)规范包含了一项关于装饰器的建议,允许注释或元数据修改类和属性。这在 TypeScript 中实现,在 Angular 2 中大量使用。在 Angular 2 集成的 Kendo UI 文档中有一些装饰器的例子。
@Component({selector: ‘my-app’})
@View({templateUrl: ‘mytemplate.html’})
class MyComponent {
}

类本身将实现逻辑以公开数据和行为。Component 声明通知 Angular2 中的类参与数据绑定并可能影响 DOM。View 声明提供有关为控件注入 DOM 的标签的信息。标签渲染后,Angular2 使用数据绑定将命令式类与其声明性标签连接起来。
声明式编程很奇怪吗?
如果你之前没有听说过 map 和 reduce 函数,你的第一感觉,我相信,就会是这样。作为程序员,我们非常习惯去指出事情应该如何运行。“去遍历这个 list”,“if 这种情况 then 那样做”,“把这个新值赋给这个变量”。当我们已经知道了如何告诉机器该如何做事时,为什么我们需要去学习这种看起来有些怪异的归纳抽离出来的函数工具?
在很多情况中,命令式编程很好用。当我们写业务逻辑,我们通常必须要写命令式代码,没有可能在我们的专项业务里也存在一个可以归纳抽离的实现。
但是,如果我们花时间去学习 (或发现) 声明式的可以归纳抽离的部分,它们能为我们的编程带来巨大的便捷。首先,我可以少写代码,这就是通往成功的捷径。而且它们能让我们站在更高的层面是思考,站在云端思考我们想要的是什么,而不是站在泥里思考事情该如何去做。
总结
声明式标记和命令式代码的概念非常重要,因为它们不仅影响你如何构建解决方案,还影响团队成员如何协作和并行工作以交付和维护解决方案。在本文中,讨论了这两种方法的不同之处,并分享了一些案例研究来证明它们的好处。
在本系列的下一篇文章中,将深入探讨另一个概念:数据绑定,它弥补了声明式标记和命令式代码之间的差距。我将解释为什么我认为支持数据绑定到 JavaScript 和 HTML 堆栈的形式化框架的引入是自 2006 年引入 jQuery 以来对 web 开发最重要的改进之一。我还将分享案例研究,展示如何用与传统的基于命令式代码的方法大不相同的数据绑定思维来构建应用程序。

原文:https://www.telerik.com/blogs…
你的点赞是我持续分享好东西的动力,欢迎点赞!
一个笨笨的码农,我的世界只能终身学习!
更多内容请关注公众号《大迁世界》!

正文完
 0