`
乡村里的一条土狗
  • 浏览: 70757 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

如何避免产生赋值语句

阅读更多

我在之前的文章里谈论过赋值语句的危害性。 使用赋值语句会使程序变的冗长,更难理解。但事实上,赋值语句对编程来说是一种基本语句,想限制它的使用几乎是不可能的。幸运的是,我们实际上是能做到的,下面我就会向你展示如何去做。

 

用正确的方式初始化

// 错误       |  // 正确
int x;        |
// ...        |  // ...
x = init();   |  int x = init();

“正确”方式的主要优点是你能很方便的浏览x 的定义的同时知道它的值。这样也能保证x 始终处在一个固定变量状态,大多数的编译器都能检测到这种状态。其次,这样可以使代码减少冗余。

“错误”方式之所以存在完全是因为很多老式的编程语言都强制要求在程序的开始处先声明变量。这样编译器好处理。但现在这已经不是问题了,即使在C语言里。

构造新数据

// 错误          |  // 正确
int x = init();  |  int x = init();
// ...           |  // ...
x = something(); |  int y = something();

这样做很重要。它能保证变量被定义后不会被改变。不留任何机会。x 的值我们可以保证它是通过init() 初始化的值。

人们使用“错误”方式一般有两个原因:高效和简洁。效率并不是个问题,现代编译器能够通过给变量重新分配地址来优化性能。而由于简洁而导致的语义模糊是得不偿失的。

用函数,不要用过程

// 错误                  |  // 正确
void to_utf8(string s);  |  string to_utf8(string s);
                         |
// ...                   |  // ...
                         |
string s1 = latin();     |  use_string(to_utf8(latin()))
to_utf8(s1);             |
use_string(s1);          |

“正确”方式使用的是一个普通的数字函数:它接受输入值,返回计算后的值。另一边,“错误”方式使用了过程 。跟函数不一样,过程不仅会影响返回的结果,还能影响其它数据,例如,过程中可以修改它的参数值。这会使这些参数很容易被污染。

所以,当能够使用函数的时候,尽量不要使用过程。你的程序这样会变得更简单。这种技巧可以让你避免去思考如何去做 (变换一个字符串),而是如何被做 (一个变换了的字符串)。要着眼于最终结果,而不是处理过程。

“错误”方式之所以存在完全是由于许多老的编程语言很难处理复杂的返回值。你只能返回单个数字。所以,当需要一个内容更丰富的返回值时,你只能在过 程中达到这个目的。而真正的返回值通常是一些简单的错误标号代码。然而现在不同了,返回复杂的结果已经不再是个问题。即使是在C语言里你也可以返回复杂的 结果。

固化你的对象

在很多的入门级的介绍面向对象编程的课程中,你能看到这样一个著名的二维坐标的例子:

// 非常非常错误
class Point
{
public:
  // constructor
  Point() { x = 0; y = 0; }

  float get_x() { return x; }
  float get_y() { return y; }

  void set_x(float new_x) { x = new_x; }
  void set_y(float new_y) { y = new_y; }

  move(Point p) {
    x = x + p.x;
    y = y + p.y;
  }
private:
  float x; float y;
};

这样设计的原因很简单:你可以通过构造函数创建一个新的坐标,然后通过set_x()set_y() 进行初始化。内部数据是经过封装的(private ),只能通过get_x()get_y() 来访问。还有个好处是,你可以通过move() 方法来移动这个坐标点。

然而,从代码本身看,却是没必要的复杂化了,而且有几个主要的错误:

  • 构造函数直接把xy 初始化成0了。如果你希望它是其它值,你还需要手工的设置。你不能初始时做到这些
  • 操作一个点的缺省方法就是修改它。这是一种赋值操作。你被限制了创建一个新值
  • set_x() , set_y() , 和 move() 方法现场修改这个对象。这些是过程 , 不是函数
  • x (和 y ) 是私有的,但你可以通过get_x()set_x() 操作它们。所以,你认为你是封装了它们,而实际上没有。
  • move() 这个方法不需要放在Point 类里。放在类里使类的体积变大,影响理解和使用。

正确的设计更简单,而且不失功能:

// 正确的
class Point
{
public:
  // constructor
  Point (float x, float y) {
    _x = x; _y = y;
  }

  x() { return x; }
  y() { return y; }

private:
  float _x; float _y
}

Point move(Point p1, Point p2) {
  return Point(p1.x() + p2.x(),
               p1.y() + p2.y());
}

另外,如果你愿意,你可以把_x_y 声明成public和常量。

使用纯功能性数据结构

从上面的介绍里我们说明了应该构建新数据 。这个建议即使是大数据结构也是有效的。意外吗,它并不是像你想象的那样失去作用。有时候你为了避免每次都拷贝整个数据结构,你可能要使用修改操作。而数组和hash table就是属于这种情况的。

这种情况下你应该是使用我们所谓的纯功能性数据结构。如果你想对这有所了解,Chris Okasaki’s thesis (也是同名著作)是个好的教材。这里,我只给大家简单的讲讲linked list。

一个链接表要么是个空表,要么是其中有个单元格存着一个指向另一个表的指针。

┌───┬───┐   ┌───┬───┐
│ x │  ───> │ y │  ───> empty
└───┴───┘   └───┴───┘

这样的数据结构如果在ML语言里是很好设计出来的,但在以类为基础的语言里会稍微有点复杂:

-- Haskell
-- A list is either the Empty list,
-- or it contains an Int and a List
data List = Empty
          | NotEmpty Int List

-- utility functions

is_empty Empty         = true
is_empty NotEmpty x xs = false

head Empty         = error
head NotEmpty x xs = x

tail Empty         = error
tail notEmpty x xs = xs

// Java(ish)
class List
{
public:
  // constructors
  List() { _is_empty = true; }
  List(int i, List next) {
    _i        = i;
    _next     = next;
    _is_empty = false;
  }

  bool is_empty() { return _is_empty; }

  int head() {
    if (_is_empty) error();
    return _i;
  }

  List tail() {
     if (_is_empty) error();
     return _next;
  }

private:
  int  _i;
  List _next;
  bool _is_empty;
}

你可以看到,现在这个List类是不可变的。我们不能修改List 对象。我们只能在现有的对象外新建新的List。 这很容易 。因为当你构建一个新List时,它会共享现有的大多数的单元。假设我们有个list l ,和一个整数i

    ┌───┬───┐   ┌───┬───┐
l = │ x │  ───> │ y │  ───> empty
    └───┴───┘   └───┴───┘

i = 42

此时,在l的顶部加入i ,这样就会产生一个新的list l2

     ┌───┬───┐
l2 = │ i │   │
     └───┴─│─┘
           │
           │   ┌───┬───┐   ┌───┬───┐
l  =       └──>│ x │  ───> │ y │  ───> empty
               └───┴───┘   └───┴───┘

或者,在代码里:

List l  = List(x, List(y, List()));
int  i  = 42;

List l2 = List(i, l); // cheap

l 仍然存在,不可变,而新建的l2 只是多了一个新建的单元。类似的,删除顶部的元素也是不费任何资源的容易。

当我们不能这样做时

有时,你不能避免赋值操作,或者受其它因素限制。也许是你需要更高的效率,你必须修改数据状态来优化程序。或者由于一些外界因素影响,比如一个用户。或者由于你使用的语言不能自动处理内存使用,这些都会阻止你使用纯功能性的数据结构

这种情况下你所能做的最好的方式是隔离那些程序中不合规范的代码(那些使用赋值语句的代码)。比如说,你想给一个数组排序,你必须用quicksort。Quicksort严重的依赖于变换转移操作,但是你可以隐藏这些操作:

array pure_sort (array a)
{
  array a2 = copy(a);
  quicksort(a2); // modify a2, nothing else
  return a2;
}

于是,当pure_sort() 这个内部函数不能按照我的建议的去写时,影响并不大,因为它被限制在函数内了。最终,pure_sort()的 行为就像是个普通的函数 了。

相反的,当你与其它业务有交互时,要小心的将交互部分的代码和运算部分的代码分隔开。比如你要写段在屏幕上画个点的程序,而且能根据鼠标的移动而移动。写出来可能会是这样:

// 错误

Point p(0, 0);
wile(true) // loop forever
{
  p = move(p, get_mouse_movement());

  if (p.x() < 0   ) p = Point(0    , p.y());
  if (p.x() > 1024) p = Point(1024 , p.y());
  if (p.y() < 0   ) p = Point(p.x(), 0    );
  if (p.y() > 768 ) p = Point(p.x(), 768  );

  draw(p);
}

这里有个错误,它在主程序里对越界坐标进行了检查。更好的方式是这样:

// 正确

point smart_move(point p, point mouse_movement)
{
  float x = p.x() < 0    ? 0
          : p.x() > 1024 ? 1024
          :                p.x();

  float y = p.y() < 0   ? 0
          : p.y() > 768 ? 768
          :               p.y();

  return Point(x, y);
}

// 主程序
Point p(0, 0);
wile(true) // loop forever
{
  p = smart_move(p, get_mouse_movement());
  draw(p);
}

现在,主程序变得更简单了。运算部分,smart_move() ,可以进行单独测试,甚至可以在其它地方重用。 现在,如果你不喜欢这样的三元操作的语法,不想按我介绍的规则,不去构造新数据

// 这样也不是很差

point smart_move(point p, point mouse_movement)
{
  float x = p.x();
  float y = p.y();

  if (x < 0   ) x = 0;
  if (x > 1024) x = 1024;
  if (y < 0   ) y = 0;
  if (y > 768 ) y = 768;

  return Point(x, y);
}

不管你怎么写,smart_move() 始终应该是个函数

结论

我说的这些都是关于降低耦合的技巧。每个程序都应该有很清晰的内部边界。每个模块应暴露最少量的接口。这能使程序更易于理解和使用。避免使用赋值语句,坚持对象恒定的原则能使接口清晰明确。但这也不是银弹,这只是辅助手段。很有用的辅助手段。

 

译文来源:外刊IT评论

 

:)

 

分享到:
评论
14 楼 yecq 2010-08-21  
减少变量被修改的机会,这种思想很好。函数式编程的很多概念值得学习借鉴
13 楼 lxs647 2010-08-04  
个人理解下来觉得没有必要这样儿,有时候像楼主口中的赋值语句的定义是必须的、、、
12 楼 lyw985 2010-08-04  
不是java,为什么发到这
11 楼 tllyf 2010-08-03  
翻译文章理解很好!  值得学习!!  不过人的思路是活的  不要禁锢
10 楼 xiaoyao312 2010-08-03  
这可得研究的很深了
9 楼 徐风子 2010-08-03  
jslfl 写道
我有点疑问,既然变量不让它变了,还叫变量吗

那就叫常量。

其实都是炒的冷饭了,函数式编程里面早就这样了,完全的函数式编程不允许使用变量的,半截的函数式编程也是不推荐使用变量。

用习惯了就很舒服了,不用害怕多线程问题,一个 名称 x,可以漫天放,反正他永远只会是那一个值。
8 楼 snake1987 2010-08-03  
Crusader 写道
我怎么觉得这框框条条,一概而论的文章很傻?

+1
你可以说推荐如何如何去做
直接说这种做法那种做法错误,非常非常错误,偏偏写出的理由又只能说明该做法不太好
7 楼 IcyFenix 2010-08-03  
曾经有一段时间我们公司的checkstyle开了变量要求final这条规则,巨烦人。
6 楼 jslfl 2010-08-03  
我有点疑问,既然变量不让它变了,还叫变量吗
5 楼 hatedance 2010-08-03  
总结起来,在java里,就是全部用final关键词。
4 楼 piao_bo_yi 2010-08-03  
函数式编程的价值观,语言的副作用会导致错误的发生,所以小心的避开状态,和OO完全相反的思路。对大数据量做处理时,性能和易用的分布式,是其超级亮点。花一些时间,熟悉一下思路,编程水平的确就会提高很多。
3 楼 Crusader 2010-08-03  
我怎么觉得这框框条条,一概而论的文章很傻?
2 楼 oneteams 2010-08-03  
<p>按照你的说法,一个变量一旦定义,就不需要再修改,如果需要同样的变量,那么就重新定义一个。我可以这么理解吗?</p>
1 楼 czxiyj 2010-08-03  
翻译的不错,希望这种译文能多一些。

相关推荐

    matlab实现四旋翼无人机自抗扰姿态容错控制-飞行器控制-四旋翼无人机-自抗扰控制-UAV-扰动识别-matlab

    内容概要:文章深入探讨了四旋翼无人机(UAV)在复杂环境下面临的挑战,特别是在面对风力、气流及传感器故障等情况时的稳定性问题。通过引入自抗扰姿态容错控制策略,解决了传统PID控制方法鲁棒性和容错能力不足的问题。该控制策略涵盖传感器测量、姿态估计、理想模型构建、扰动识别、控制设计及自抗扰控制等内容,并利用MATLAB进行了详细仿真实验,验证了策略的有效性。具体而言,传感器数据经过卡尔曼滤波融合处理,提高了姿态估计准确性;采用扩张状态观测器(ESO)实时估算扰动,增强抗干扰能力;通过故障检测与恢复机制,确保飞行安全可靠。 适用人群:从事无人飞行器研究的技术人员、高校教师及研究生,特别是关注无人机控制系统设计和优化的人群。 使用场景及目标:适用于科研实验室及工业环境中对四旋翼无人机飞行控制系统的开发测试;目标是在提高四旋翼无人机飞行稳定性和可靠性的同时,优化控制参数,满足不同场景的任务需求。 其他说明:文中不仅介绍了理论知识和技术细节,还给出了详细的MATLAB源代码实现方式,帮助读者更快理解和实践相关概念。同时指出未来的研究方向,为进一步探索四旋翼无人机的控制技术和实际应用提供指导。

    【毕业设计】Python-Django-html深度学习文本相似度检测系统(bert)源码(完整前后端+mysql+说明文档+LW+PPT).zip

    【毕业设计】Python-Django-html深度学习文本相似度检测系统(bert)源码(完整前后端+mysql+说明文档+LW+PPT).zip

    外卖侠v5.0.5小程序源码+前端.zip

    外卖侠小程序v5.05 前端 1、修复美团联盟不显示二维码的问题; 2、修复电商搜索京东商品翻页商品重复的问题; 3、优化联营用户同步机制; ***本次更新【需要】重新上传前端审核*** 外卖模块使用文档: https://docs.qq.com/doc/DS0JmWFNYdHZZWEVS 外卖模块z新全套视频教程: 链接:https://pan.baidu.com/s/1qMBzn-csUTpTFZ4hekbMtQ 提取码:waim 外卖侠小程序v5.05 前端%插图%

    web api 全局动态路由处理

    web api 全局动态路由处理 一个api controller通过全局配置,适配多个路由,

    VSTD 19-1 Flammability of the interior materials for motor vehicle.pdf

    VSTD 19-1 Flammability of the interior materials for motor vehicle.pdf

    基于改进粒子群算法的微电网多目标优化调度策略:降低能耗与环保成本的有效方案,基于改进粒子群算法的微电网多目标优化调度策略:降低能耗与环保成本的有效方案,基于改进粒子群算法的微电网多目标优化调度 微电

    基于改进粒子群算法的微电网多目标优化调度策略:降低能耗与环保成本的有效方案,基于改进粒子群算法的微电网多目标优化调度策略:降低能耗与环保成本的有效方案,基于改进粒子群算法的微电网多目标优化调度 微电网优化调度作为智能电网优化的重要组成部分,对降低能耗、环境污染具有重要意义。 微电网的发展目标既要满足电力供应的基本需求,又要提高经济效益和环境保护。 对此,提出了一种综合考虑微电网系统运行成本和环境保护成本的并网模式下微电网多目标优化调度模型。 同时采用改进的粒子群算法对优化模型进行求解。 仿真结果表明,该模型可以有效降低用户的用电成本和环境污染,促进微电网的优化运行,并验证了改进的粒子群算法的优越性能 ,基于改进粒子群算法;微电网多目标优化调度;微电网优化;降低能耗;环境保护成本。,基于改进粒子群算法的微电网多目标优化调度与环境保护的协同策略

    清华大学DeepSeek手册:政企与创业者必备的AI发展指南及应用路径

    内容概要:本文详细解读了清华大学DeepSeek课堂内容,探讨AI在政企、创业领域的重大发展机遇与应用场景,特别强调了大模型的发展及其对企业转型和智能化的重要意义。文中通过具体案例介绍了AI在各个行业的应用,包括但不限于智能制造、科学研究等,突出了AI在重塑社会经济结构中的关键地位,并针对当前和未来的AI应用提出了明确的发展路径与策略指导。 适合人群:政府机构工作人员、企业管理者、创业者和技术研发人员。 使用场景及目标:用于理解AI在政企中的应用场景,探索基于DeepSeek的企业转型升级策略;帮助企业制定智能化发展规划;推动科技创新和社会经济发展。 其他说明:课程内容还涉及AI的安全性和伦理道德等问题,提供了关于构建负责任的AI系统的见解,并对未来可能出现的新形势给予了预测与分析。

    SpringBoot 3.x + Netty + MQTT 实战物联网智能充电桩

    一、什么是物联网(IoT) 物联网(Internet of Things,简称 IoT)是指通过互联网连接和通信的物理设备和对象的网络。它是一个由传感器、软件和通信设备组成的系统,可以使各种设备和物品相互连接,并通过数据交换和分析来提供更智能、高效和自动化的功能。 物联网的主要目标是将真实世界的物体与互联网相连,使其具备感知、交互和通信的能力。通过物联网,可以实现智能家居、智慧城市、工业自动化、农业监测、智能交通等应用。 二、为什么需要物联网? 物联网的出现主要是为了解决日常生活和工作中的一些实际问题。例如,在智能家居中,可以通过物联网连接家中的各种设备,如智能灯泡、智能插座、智能门锁等,从而实现远程控制、自动化调节和能源管理,提高家居的舒适度和能源利用效率。 此外,物联网在工业领域也发挥着重要作用。传统的工业生产过程通常需要大量的人力和物力投入,而物联网可以通过连接和监控各种设备和环境参数,实现生产过程的自动化和优化,提高生产效率和产品质量。例如在石油行业,可以利用物联网技术来监测阀门的状态和运行情况。通过安装传感器和执行器在阀门上,可以实时监测阀门的开启、关闭状态、温度、压力

    通过使用AGM迭代公式和高精度数学计算开源库gmp计算圆周率小数点后1000位C语言代码

    通过使用AGM迭代公式和高精度数学计算开源库gmp计算圆周率小数点后1000位C语言代码

    DDR4 DQ 不同参数 波形眼图对比 20230505

    仿真笔记

    【毕业设计】基于Python的Django-html开放领域事件抽取系统源码(完整前后端+mysql+说明文档+LW+PPT).zip

    【毕业设计】基于Python的Django-html开放领域事件抽取系统源码(完整前后端+mysql+说明文档+LW+PPT).zip

    奶牛的行为识别数据集,可识别牛在吃,躺着,站着,喝水等行为,平均正确识别率在83.0%, yolov11格式标注

    奶牛的行为识别数据集,可识别牛在吃,躺着,站着,喝水等行为,平均正确识别率在83.0%, yolov11格式标注

    轻量级年会抽奖系统源码

    项目采用轻量级架构,采用的是Springboot3.x、html + elementUI + vue.js + Three.js + axios、H2 database、jdk21等技术实现,接口请求访问使用了jwt验证,直接运行即可使用

    hymenoptera-data 数据集

    hymenoptera_data

    【毕业设计】SpringBoot+vue仓储物流管理系统【源码+论文+答辩ppt+开题报告+任务书】.zip

    【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。

    移动应用开发全攻略:工具选用、环境搭建与调试技巧

    内容概要:本文详述了移动应用开发过程中所需的各种工具、环境设置方法以及调试技术与注意事项。首先介绍了常见的几种开发工具——针对iOS和Android的原生开发工具如Xcode和Android Studio,以及跨平台开发工具如React Native、Flutter、Xamarin,还有适用于初学者的低代码平台Appery.io。其次讨论了不同系统下开发环境的具体配置步骤及其所用到的重要集成开发环境。接着,讲解了几种主流的调试手段,从网络请求、性能瓶颈、布局展示等方面提出针对性解决办法。最后指出开发时要注意用户实际操作体验的安全便捷性和法规遵从等问题。 适用人群:有一定编程基础和技术背景的学习者,想要深入移动开发领域的工程师。 使用场景及目标:①作为新手或转行者学习移动应用程序的制作;②有经验开发者希望提升效率,改善产品品质;③企业内部培训资料帮助成员掌握最新的技术和规范标准。 其他说明:移动开发领域不断变化更新速度快,在理解和掌握了文中提到的内容基础上还需要持续关注行业的前沿动态和技术进步。

    Deepseek满血版API接入

    Deepseek满血版API接入

    MATLAB下电转气协同与碳捕集垃圾焚烧虚拟电厂优化调度复现程序及仿真结果展示,MATLAB下电转气协同与碳捕集垃圾焚烧虚拟电厂优化调度复现程序及仿真结果展示,MATLAB代码:计及电转气协同的含碳捕

    MATLAB下电转气协同与碳捕集垃圾焚烧虚拟电厂优化调度复现程序及仿真结果展示,MATLAB下电转气协同与碳捕集垃圾焚烧虚拟电厂优化调度复现程序及仿真结果展示,MATLAB代码:计及电转气协同的含碳捕集与垃圾焚烧电厂优化调度 关键词:碳捕集 电厂 需求响应 优化调度 电转气协同调度 参考文档:《计及电转气协同的含碳捕集与垃圾焚烧电厂优化调度》复现程序 仿真平台:MATLAB+CPLEX 使用的是yalmip+cplex求解器完成求解,拿后前可以看运行结果,可以看参考文档 ,碳捕集; 虚拟电厂; 需求响应; 优化调度; 电转气协同调度; YALMIP+CPLEX求解器; MATLAB复现程序。,基于YALMIP+CPLEX的碳捕集与垃圾焚烧虚拟电厂协同优化调度程序

    永磁同步电机PMSM模型搭建与Simulink仿真分析,自己搭建的Simulink永磁同步电机PMSM模型解析与实践体验,自己搭的永磁同步电机PMSM模型 simulink模型 ,PMSM模型; Si

    永磁同步电机PMSM模型搭建与Simulink仿真分析,自己搭建的Simulink永磁同步电机PMSM模型解析与实践体验,自己搭的永磁同步电机PMSM模型 simulink模型 ,PMSM模型; Simulink模型; 永磁同步电机模型搭建。,基于Simulink的PMSM(永磁同步电机)模型构建与仿真

    使用C语言实现的矩阵初等行变换算法,支持分数运算,手撸算法展现独特优势,C语言实现矩阵初等行变换算法,支持分数运算,纯手撸算法展示,使用纯C语言编写的计算矩阵初等行变算法实现 算法比较完善,可以支持

    使用C语言实现的矩阵初等行变换算法,支持分数运算,手撸算法展现独特优势,C语言实现矩阵初等行变换算法,支持分数运算,纯手撸算法展示,使用纯C语言编写的计算矩阵初等行变算法实现。 算法比较完善,可以支持分数输入以及运算 懂的人自然知道有多方便(尽管MATLAB同样可以实现,此算法的优势主要引入分数运算,纯手撸,很舒服) 效果看图: ,C语言; 矩阵初等行变换算法; 分数输入; 分数运算; 算法实现。,基于C语言的手动计算矩阵初等行变换与分数运算算法实现

Global site tag (gtag.js) - Google Analytics