谈一谈无状态和无副作用
概述
在开发系统的时候,我们经常会听到无状态,无副作用,以及纯函数等概念。这些概念经常被人忽视,我结合自己的工作经验,为大家来谈一下我的理解。
无状态
对于无状态这个词,我们听到最多的是无状态函数
无状态函数
这个概念是我们最常听到的,无状态函数的意思是
一个给定的函数,在任意时刻,对它使用相同的入参,其返回结果一定是不变的。
一个简单的例子
function add(a,b) {return a + b;
}
类似这样的例子,非常简单,因为无论任何时候, a + b 的值都只由 a 和 b 决定。
类似的一个有状态的函数
var temp = 3;
function addV2(a ,b) {return a + b + temp;
}
在这个例子里,addV2 对返回的结果,并不总是唯一的,因为一旦修改 temp 的值,则 addV2 方法的返回结果就会修改,所以这样的方法不是无状态的。无状态函数,在一些地方被称为是纯函数
。
为什么我们提倡无状态
无状态最大的好处在于高内聚,对于一个高内聚的方法,带来的一个极大的好处就是可以快速组件化,在分布式系统里,无状态往往意味着可以快速的进行水平扩容,只需要添加机器便可。并且无状态还带来一个好处,就是便于测试,因为对于无状态函数来说,测试只需要构造入参就好了,但是有状态的函数普遍更难构造上下文,也更难以去测试。
状态的必然性
虽然无状态的方法或者对象有很多好处,但是实际上在真实的业务场景中,我们写的代码,几乎都是有状态的。原因很简单,因为我们服务的用户是不断在变化的。举个例子,我们现在提供一个用户查询订单的方法,这个方法的入参是用户的身份信息,但是这个方法不可能是无状态的,因为用户会购买商品,产生新的订单,你的方法返回的结果必然是要改变的。
之所以对象无法是无状态的,本质的原因是因为对象的返回结果,除了依赖输入外,还依赖其它外部的对象,而外部的对象是有状态的。
无副作用函数
一个函数如果他的返回值依赖了除入参外的其它变量,那么他会是有状态的函数。而如果一个函数在运行的过程中,除了返回变量,还修改了其它函数体外的变量,那么这个函数被称为有副作用的函数。
var temp = 1;
function addV3(a,b){temp = temp + 1;return temp + a + b;
}
见上方函数,这个函数会在每次执行的过程中修改 temp 的值,有这样行为的函数,我们称为有副作用的函数。但是 addV2 函数却是一个无副作用的函数,因为他只读 temp 这个外部变量,而不会修改它。
为什么要倡导无副作用的函数
倡导无副作用的函数,主要原因是因为副作用操作,不利于代码的阅读,因为我们在阅读到一份使用了有副作用的代码的时候,我们将难以正确的评估函数造成的影响。这往往是出现 Bug 的地方。如果函数式无副作用的,我们仅仅只需要关注它的输出就好了。
不可变对象
一个对象往往有很多属性值,如果一个对象的某个属性 field 的值是 a, 我们希望将他变为 b,最简单的方案是直接修改 field,field = 2。 这样的操作看起来是非常简单的。在大多数应用中这样都是没有问题的,但是在并发系统中,我们却更倾向于使用不可变对象。
所谓不可变对象,指的是对象的属性是不可以被修改的,如果需要修改属性,则创建
一个新的对象,这个对象相应的属性的值变为需要的新值
并发系统为什么倾向于使用不可变对象
因为并发系统中,常常会有多个线程读取同一个对象的情况,如果线程 A 修改了对象,那么极有可能导致线程 B 读取错误。举一个笔者的亲身经历,我们现在做一个新闻系统,热点的新闻被缓存在 LocalCache 中,这个 LocalCache 可以是 guava cache 等实现。
现在我们一个需求,是开发一个展示热点新闻列表的接口,这个时候,从 LocalCache 中获取到的新闻数据结构如下
[
{
"title":"北斗卫星发射",
"content":"2018 年某一天,北斗系统发送了第 ...."
},
{
"title": ”xxx 公司股价大涨 5%",
"content":"2019-01-02 xxx 公司,因为 YYY ..."
}
]
对于一个新闻列表的数据接口来说,正文 "content” 是不需要的,仅仅需要标题就好了,如果在列表接口中返回 "content” 字段的内容,那么将会产生大量的流量浪费,这个时候我们可以将 content 置为 null , 那么就能节约大量的流量。我们的代码可以这么写
//以下用 Java 代码举例
List<Long> hotNewsId = Arrays.asList(1L,2L,3L);
List<NewVO> news = localCache.get(hotNewsId);
//将 content 置为 null
new.foreach(vo -> vo.setContent(null));
return news;
这样写看起来没有问题,读列表接口性能也很好,但是这样写却出问题了,这样写完之后,打开文章详情页,结果发现文章的正文居然为空了。排查后发现,原因就是因为并发读写的问题。
因为 NewVO 被缓存了,读取列表的时候,content 被置为空,结果当 localCache 中的缓存没有更新的时候,另一个打开文章详情的请求,获取到了这个 content 已经被置为空的对象,导致文章详情页为空。
正是为了避免类似这样的问题发生,在并发系统中,我们倾向于使用不可变对象,通常来说,除非创建对象开销极大,再考虑共享。
谈一谈无状态和无副作用相关推荐
- HTTP协议是无状态协议,怎么理解?
2019独角兽企业重金招聘Python工程师标准>>> Http是一个无状态协议,同一个会话的连续两个请求互相不了解,他们由最新实例化的环境进行解析,除了应用本身可能已经存储在全局对 ...
- 郑晔:代码之丑 无状态方法
2019独角兽企业重金招聘Python工程师标准>>> 诸位Java程序员,想必大家对SimpleDateFormat并不陌生.不过,你是否知道,SimpleDateFormat不是 ...
- 【转】多线程之有状态对象和无状态对象
有状态对象和无状态对象 参考:https://www.cnblogs.com/xubiao/p/6567349.html 一. 基本概念 1. 什么是有状态对象 有状态对象指的是有数据存储功能的类的对 ...
- HTTP协议是无状态协议,怎么理解
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp24 HTTP协议是无状态协议,怎么理解? 2010-02-23 09:4 ...
- 状态服务器与无状态服务器(要点)
对服务器程序来说,究竟是有状态服务,还是无状态服务,其判断依旧是指两个来自相同发起者的请求在服务器端是否具备上下文关系.如果是状态化请求,那么服务器端一般都要保存请求的相关信息,每个请求可以默认地使用 ...
- 无状态编程, lambda 表达式中传入的局部变量,为什么需要是不可变的(final)
无状态编程 说明 @author JellyfishMIX - github / blog.jellyfishmix.com LICENSE GPL-2.0 前言 本文将会根据以下顺序进行叙述: la ...
- 有状态,无状态对象是什么概念
基本概念: 有状态就是有数据存储功能.有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的.在不同方法调用间不保留任何状态. 无状态就是一次操作,不能保存数据 ...
- 应用的无状态和有状态!
应用的无状态和有状态! 什么是有状态和无状态 ? 场景: 当用户登录时,将session或者token传给应用服务器管理,应用服务器里持有用户的上下文信息,这时应用服务器是有状态的 . 同样用户登陆时 ...
- http的无连接与无状态
HHTP协议一共有五大特点:1 支持客户/服务器模式 :2 简单快速 : 3 灵活 : 4 无连接 : 5 无状态 . 其中无状态是其最重要的特点之一,因此常说HTTP是一种无状态协议. . 那么在这 ...
最新文章
- 打破认知:程序设计 #x3D; 算法 + 数据结构?
- AndroidStudio导入项目一直卡在Building gradle project info最快速解决方案
- 代码段编辑器SnippetEditor 2.1
- 2018-2019-1 20165231 实验四 外设驱动程序设计
- kvm vnc的使用,鼠标漂移等
- cocos2d-x,求世界坐标
- Maven 插件开发
- CentOS 7安装Gnome GUI 图形界面
- 2015第28周六SVN和Git
- MPLS 配置静态LSP
- 全拼到缩写月份单词python_月份的英文缩写及全名
- 计算机31进制表,74ls290构成31进制计数器电路图文详解
- caffe 训练笔记总结
- Gensim官方教程翻译(五)——英文维基百科的实验
- 微信小程序进行地图导航,地图展示功能
- github 本地 fatal: couldn‘t find remote ref master错误解决方案
- 微信怎么发超过200的大额红包?
- 使用私服管理jar时,下载jar出现 lastUpdated问题 maven
- matlab粒子群加约束条件_粒子群算法(PSO)MATLAB实现
- jQuery前端开发学习指南(11)——jQuery属性过滤选择器