通俗解释一下“强静态类型”

我写软件已经有 20 多年了,随着时间的推移,我越来越确信强静态类型不仅是一个好主意,而且几乎总是正确的选择。

非类型化语言(或语言变体)肯定有用途,例如,当使用 REPL 时,或者在已经无可救药的无类型环境(例如 shell)中使用一次性脚本时,它们会更好。然而,在几乎所有其他情况下,强类型都是首选。

不使用类型是有好处的,比如更快的开发速度,但与所有的好处相比,它们就显得微不足道了。对此,我要说:

编写没有类型的软件可以让你全速前进——全速冲向悬崖。

关于强静态类型的问题很简单:你是愿意多做一点工作,在编译时检查不变量(或非编译语言的类型检查时间),还是愿意少做一点工作,在运行时强制执行它们,或者更糟糕的是,即使在运行时也不强制执行(JavaScript,我在看着你…)。

在运行时出错是一个糟糕的想法。首先,这意味着在开发过程中你不会总是抓住它们。其次,当你抓住他们的时候,它会以面向客户的方式发生。是的,测试有帮助,但是考虑到无限的可能性,为每一个可能的错误类型函数参数编写测试是不可能的。即使可以,拥有类型也比测试错误类型容易得多。

1、类型导致更少的错误

类型还为代码提供注释,使人类和机器都受益。拥有类型是一种更严格地定义不同代码段之间协定的方法。

请考虑以下四个示例。它们都做完全相同的事情,只是契约定义级别不同。

// Params: Name (a string) and age (a number).function birthdayGreeting1(...params) {    return `${params[0]} is ${params[1]}!`;}
// Params: Name (a string) and age (a number).function birthdayGreeting2(name, age) {    return `${name} is ${age}!`;}
function birthdayGreeting3(name: string, age: number): string {    return `${name} is ${age}!`;}

复制代码

第一个甚至没有定义参数的数量,因此如果不阅读文档,很难知道它的作用。我相信大多数人都会同意第一个是令人讨厌的,不会写这样的代码。虽然它的思想与类型非常相似,但它是关于定义调用者和被调用者之间的契约。

至于第二个和第三个,由于类型的原因,第三个将需要更少的文档。代码更简单,但不可否认,优点相当有限。好吧,直到你真正更改这个函数前......

在第二个和第三个函数中,作者假设年龄是一个数字。因此,更改代码绝对没问题,如下所示:

// Params: Name (a string) and age (a number).function birthdayGreeting2(name, age) {    return `${name} will turn ${age + 1} next year!`;}
function birthdayGreeting3(name: string, age: number): string {    return `${name} will turn ${age + 1} next year!`;}

复制代码

问题是使用此代码的某些位置接受从 HTML 输入(因此始终是字符串)收集的用户输入。这将导致:

> birthdayGreeting2("John", "20")"John will turn 201 next year!"

复制代码

虽然类型化版本将无法正确编译,因为此函数将年龄除外,否则年龄是数字,而不是字符串。

在调用方和被调用方之间建立协定对于代码库非常重要,这样调用方就可以知道被调用方何时更改。这对于开源库尤其重要,因为调用方和被调用方不是由同一组人编写的。没有这个合同,就不可能知道事情在发生时是如何变化的。

2、类型带来更好的开发体验

IDE 和其他开发工具也可以使用类型来极大地改善开发体验。如果你的任何期望是错误的,你将在编写代码时得到通知。这大大降低了认知负荷。你不再需要记住上下文中所有变量和函数的类型。编译器将与你同在,并在出现问题时告诉你。

这也带来了一个非常好的额外好处:更容易重构。你可以相信编译器会让你知道你所做的更改(例如上面示例中的更改)是否会破坏代码中其他地方所做的假设。

类型还可以使新工程师更容易加入代码库或库:

  • 他们可以遵循类型定义来了解事物的使用位置。

  • 修改东西要容易得多,因为更改会触发编译错误。

让我们考虑对上述代码进行以下更改:

class Person {  name: string;  age: number;}
function birthdayGreeting2(person) {    return `${person.name} will turn ${person.age + 1} next year!`;}
function birthdayGreeting3(person: Person): string {    return `${person.name} will turn ${person.age + 1} next year!`;}
function main() {  const person: Person = { name: "Hello", age: 12 };
  birthdayGreeting2(person);
  birthdayGreeting3(person);}

复制代码

很容易查看(或使用 IDE 查找)所有使用过的位置。你可以看到它被启动,你可以看到它被使用。然而,为了知道它的用途,你需要阅读整个代码库。

这样做的另一方面是,在看的时候,很难知道它期望 a 作为参数。其中一些问题可以通过详尽的文档来解决,但是:(1)如果使用类型可以实现更多的功能,为什么还要费心呢?(2)文档过时,这里的代码是 document。

这与你不编写代码的方式非常相似:

// a is a personfunction birthdayGreeting2(a) {    b = a.name;    c = a.age;    return `${b} will turn ${c + 1} next year!`;}

复制代码

你可能希望使用有用的变量名。类型也是一样的,它只是 steriods 上的变量名。

3、我们对类型系统中的所有内容进行编码

在 Svix,我们喜欢类型。事实上,我们尝试在类型系统中对尽可能多的信息进行编码,以便在编译时捕获所有可以在编译时捕获的错误;同时也要压缩开发者体验改进的额外里程。

例如,Redis 是一个基于字符串的协议,没有固有的类型。我们使用 Redis 进行缓存(以及其他功能)。问题是,我们所有的优秀的类型优势将在 Redis 层丢失,并且可能发生 bug。

考虑下面这段代码:

pub struct Person {    pub id: String,    pub name: String,    pub age: u16,}
pub struct Pet {    pub id: String,    pub owner: String,}let id = "p123";let person = Person::new("John", 20);cache.set(format!("person-{id}"), person);// ...let pet: Pet = cache.get(format!("preson-{id}"));

复制代码

代码片段中有几个 bug:

  • 第二个键名称有个拼写错误。

  • 我们正在尝试将一个人装入宠物类型。

为了避免这样的问题,我们在 Svix 做了两件事。首先,我们要求键是某种类型的(不是泛型字符串),要创建这种类型,需要调用一个特定的函数。我们做的第二件事,是将键与值强制配对。

所以上面的例子看起来像这样:

pub struct PersonCacheKey(String);
impl PersonCacheKey {    fn new(id: &str) -> Self { ... }}
pub struct Person {    pub id: String,    pub name: String,    pub age: u16,}
pub struct PetCacheKey;
pub struct Pet {    pub id: String,    pub owner: String,}let id = "p123";let person = Person::new(id, "John", 20);cache.set(PersonCacheKey::new(id), person);// ...// Compilation will fail on the next linelet pet: Pet = cache.get(PersonCacheKey::new(id));

复制代码

这已经好多了,并且不可能出现前面提到的任何错误。虽然我们可以做得更好!

请考虑以下函数:

pub fn do_something(id: String) {    let person: Person = cache.get(PersonCacheKey::new(id));    // ...}

复制代码

它有几个问题。首先是不太清楚 id 应该用来做什么。是一个人吗?一个宠物吗?很容易意外地用错误的名称调用它,就像下面的例子一样

let pet = ...;do_something(pet.id); // <-- should be pet.owner!

复制代码

第二,我们正在失去可发现性。很难知道宠物与人有关系。

因此,在 Svix,我们为每个类型都有一个特殊的类型,以确保没有错误。调整后的代码如下所示:

pub struct PersonId(String);pub struct PetId(String);
pub struct Person {    pub id: PersonId,    pub name: String,    pub age: u16,}
pub struct Pet {    pub id: PetId,    pub owner: PersonId,}

复制代码

这确实比我们之前的例子要好得多。

4、那么为什么不是每个人都喜欢类型呢?

反对类型一方论证的主要依据是:

  • 开发速度

  • 学习曲线和类型复杂性

  • 所需的工作量和样板

首先,我认为即使上述所有情况都是真的,上面提到的优势也值得麻烦。

其次是开发速度。没有类型的原型设计肯定要快得多。你可以注释掉代码片段,并且不会让编译器向你抱怨。你可以为某些字段设置错误的值,直到你准备好找出正确的字段等。

虽然就像我上面说的:“编写没有类型的软件可以让你全速前进。全速向悬崖走去。”问题在于,这只是激进且不必要的技术债务。当你需要调试代码无法正常工作的原因时(无论在本地、测试套件或生产环境中),你都需要多次支付这笔费用。

至于学习曲线:是的,学习更多的东西需要时间。不过我得说,大多数人不需要成为类型专家。他们可以使用非常简单的类型表达式过日子,并询问他们是否曾经遇到瓶颈。然而,如果你让事情保持简单,你可能很少会碰到一个。

此外,人们已经被要求学习如何编码,学习框架(React,Axum 等),以及许多其他东西。我认为学习负担并不像人们想象的那么重。

最后,但并非最不重要的是,关于学习曲线:我坚信,不必了解类型而减少学习曲线的好处远远小于在特定代码库上使用类型脚本的好处。特别是因为学习类型是一次性的成本。

最后一点是关于在代码库中使用类型所需的工作量和样板。我坚信,比起不写类型所需要的工作量,这种工作量实际上要少得多。

不使用类型需要大量的文档和测试,才能达到基本的健康水平。文档可能会过时,测试也会过时;无论哪种方式,它们都比添加正确的类型需要更多的努力。阅读带有类型的代码也更容易,因为你可以内联获取类型,而不是在函数文档中获取类型,在函数文档中,它的格式不一致,并且增加了很多干扰。

是的,在不支持推理的语言中,类型可能是一种痛苦,例如 Java 可能很乏味:

Person person1 = newPerson();Person person2 = newPerson();Person child = makeChild(person1, person2);

复制代码

而其他具有推理功能的语言(如 Rust)则要好得多:

let person1 = new_person();let person2 = new_person();let child = make_child(person1, person2);

复制代码

因此,拥有合适的工具肯定会有所帮助。说到工具,为了获得类型的好处,你可能需要使用支持语言感知的现代代码完成的代码编辑器(或 IDE)。

5、结语

我可以在许多话题上看到双方的争论,比如 vs.,制表符 vs.空格,甚至更具争议性的主题。尽管在这种情况下,与收益相比,成本是如此之低,以至于我不明白为什么有人会选择不使用类型。我不知道自己忽略了什么,我只知道,强类型是我愿意死在上面的一座山。

6、快速开发工具推荐

这是一款基于 SpringBoot+Vue 的前后端分离的项目,麻雀虽小,五脏俱全,开箱即用!

JNPF 开发平台的前端采用 Vue.js,这是一种流行的前端 JavaScript 框架,用于构建用户界面。Vue.js 具有轻量级、可扩展性强和生态系统丰富等特点,被广泛应用于构建单页面应用程序。

后端采用 SpringBoot,这是一种基于 Java 的开源框架,用于简化 Spring 应用的初始搭建以及开发过程。SpringBoot 通过自动配置和约定大于配置的原则,简化了 Spring 应用的配置和开发。此外,JNPF 还采用 MyBatis-Plus 作为持久层框架,它是一个功能强大的 MyBatis 扩展,可以大大简化数据库操作的开发。

核心功能:表单引擎、可视化引擎、BI 引擎、流程引擎、权限引擎、门户引擎、大屏引擎、接口中心、物联平台。尝试自己开发一个应用!应用地址:https://www.jnpfsoft.com/?csdnxue

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/210154.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

有什么进销存软件能对接微信小程序?

有什么进销存软件能对接微信小程序&#xff1f; 据我所知&#xff0c;很多进销存软件都有配套的微信小程序吧。 以我们现在用的这个为例&#xff0c;这也是同行推荐过来的&#xff0c;很好用&#xff0c;而且性价比很高—— 在线平台&#xff0c;无需下载APP&#xff0c;搭载…

C语言实战演练之跳动的爱心C语言版

跳 动 的 爱 心 - LOVE - 完整程序 #include <stdio.h> #include <math.h> #include <windows.h> #include <tchar.h> float f(float x, float y, float z) {float a x * x 9.0f / 4.0f * y * y z * z - 1;return a * a * a - x * x * z * z …

ELK简单介绍一

任务背景 运维人员需要对系统和业务日志进行精准把控&#xff0c;便于分析系统和业务状态。日志分布在不同的服务器上&#xff0c;传统的使用传统的方法依次登录每台服务器查看日志&#xff0c;既繁琐又效率低下。所以我们需要集中化的日志管理工具将位于不同服务器上的日志收…

蓝桥杯日期问题

蓝桥杯其他真题点这里&#x1f448; 注意日期合法的判断 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class Main{static int[] days {0,31,28,31,30,31,30,31,31,30,31,30,31};static BufferedReader in new Buf…

【核心重点】Flink四大基石

1. Time&#xff08;时间机制&#xff09; 时间概念 处理时间&#xff1a;执行具体操作时的机器时间&#xff08;例如 Java的 System.currentTimeMillis()) &#xff09;事件时间&#xff1a;数据本身携带的时间&#xff0c;事件产生时的时间。摄入时间&#xff1a;数据进入 …

linux vim 基础设置-自动填充文件头

前言 当前为vimrc脚本设置&#xff0c;脚本位置在 ~/.vimrc or /etc/vimrc 当前为首次打开 C C Shell 文件&#xff0c;自动填充对应文件头信息&#xff0c;再次打开时会修改对应时间信息 :set nu "显示行号 :set hlsearch "搜索时 高亮"新建 .c .cpp .sh文件&a…

理解Go语言中的defer

引言 Go有许多在其他编程语言中可以找到的常见控制流关键字,例如if、switch、for等。defer是其他大多数编程语言中没有的关键字,尽管它不太常见,但你很快就会看到它在你的程序中有多么有用。 defer语句的主要用途之一是清理资源,例如打开的文件,网络连接和数据库句柄。在…

在AWS Lambda上部署EC2编译的FFmpeg工具——自定义层的方案

大纲 1 确定Lambda运行时环境1.1 Lambda系统、镜像、内核版本1.2 运行时1.2.1 Python1.2.2 Java 2 环境准备2.1 创建EC2实例 3 编译FFmpeg3.1 连接EC2 4 编译5 上传S3存储桶5.1 创建S3桶5.2 创建IAM策略5.3 创建IAM角色5.4 EC2关联角色5.5 修改桶策略5.6 打包并上传 6 创建Lamb…

智能优化算法应用:基于海鸥算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于海鸥算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于海鸥算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.海鸥算法4.实验参数设定5.算法结果6.参考文献7.MA…

【nuxt3】cannot read preperties of null (reading ‘$nuxt‘)

问题描述 vue3 中&#xff0c;通过 createVNode 创建子组件实例时&#xff0c;发现子组件无法获取到父组件中的 router、store 信息&#xff0c;一旦子组件使用就会报错。 问题原因 通过控制台断点调试&#xff0c;发现时 appContext 值为空导致的。怀疑是创建子组件的时候&a…

海外地区开启IPV6无法访问服务器问题

前言 最近有海外地区的用户反馈无法访问公司的网络&#xff0c;无法下载应用和系统进行升级。了解到浏览器可以正常访问公司域名&#xff0c;谷歌&#xff0c;油管等都能正常使用。日志分析GET请求服务器数据时没有得到应答&#xff0c;最终查询网络相关修改确认与网络IPV6有关…

掌握游戏开发的全方位知识:这些内容你一定要知道

游戏开发是一项涉及多学科的综合性工作&#xff0c;从游戏设计到编程、美术、音频、测试等多个方面都需要开发者具备广泛的知识。以下是进行游戏开发时需要掌握的主要知识领域。 首先&#xff0c;游戏设计是整个过程的基石。这包括游戏机制和玩法设计、关卡设计、用户界面&…

表示你的shell未被正确配置以使用conda activate--换成清华源anaconda

1 CommandNotFoundError: Your shell has not been properly configured to use conda activate. If using conda activate from a batch script, change your invocation to CALL conda.bat activate.To initialize your shell, run$ conda init <SHELL_NAME>这个错误提…

uniapp-获取手机型号

要获取当前设备的手机型号&#xff0c;您可以使用uni-app提供的uni.getSystemInfo() API来实现此目的。 代码示例&#xff1a; uni.getSystemInfo({success: function(res) {console.log("手机型号&#xff1a;" res.platform)} })该方法会返回一个包含设备信息的…

JFrog推出面向Hugging Face的原生集成,为 ML 模型提供强大支持,实现DevOps、安全和AI的协调统一

2023年12月5日 —— 流式软件公司、企业软件供应链平台提供商JFrog推出ML模型管理功能&#xff0c;这是业界首套旨在简化机器学习&#xff08;ML&#xff09;模型管理和安全性的功能。JFrog 平台中的全新ML模型管理功能使AI交付与企业现有的 DevOps 和 DevSecOps 实践保持一致&…

计算机评价的主要性能指标

对计算机评价的主要性能指标如下&#xff1a; 1&#xff0e;时钟频率&#xff08;主频&#xff09; 主频是计算机的主要性能指标之一&#xff0c;在很大程度上决定了计算机的运算速度。CPU 的工作节拍是由主时钟来控制的&#xff0c;主时钟不断产生固定频率的时钟脉冲&#xff…

一个简单的可视化的A星自动寻路

一个简单的应用场景&#xff0c;流程图连线 源码&#xff1a; addExample("A星路径查找", function () {return {template: <div><div ref"main"></div></div>,data() { return {}; },computed: {},methods: {},mounted() {var c…

Python中的比较两个字符串

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;字符串比较是一项常见且关键的操作&#xff0c;涵盖了诸多方法和技巧。比较两个字符串是否相等、大小写是否一致&#xff0c;或者在一个字符串中寻找特定的子字符串&#xff0c;都是日…

征途漫漫:汽车MCU的国产替代往事

01.西雁东飞&#xff0c;南下创业 1985年&#xff0c;山东大学物理系毕业的周生明加入878厂&#xff08;“北霸天”&#xff09;参与MOS电路研发&#xff0c;随后几年&#xff0c;大洋彼岸的英特尔相继推出CPU 386\486、奔腾系列等产品。在摩尔定律的凸显、进口和走私的剧烈冲…

基于Java房屋租赁管理系统

基于Java房屋租赁管理系统 功能需求 1、房源信息管理&#xff1a;系统需要能够记录和管理所有房源的详细信息&#xff0c;包括房屋地址、房屋面积、租金、付款方式、房屋类型等。管理员应该可以添加、编辑和删除房源信息。 2、租户信息管理&#xff1a;系统需要能够记录和管…