命名与空间

在所有开始之前,先强调一下: 在 coding 的世界里,没有特别情况下,都是大小写敏感的,比如 Aa 分别代表不同的东西。

基本概念

什么是变量

变量,就是一个『代词』而已。

初学者可能会误认为 变量 只有文本、数字这些类型,实际上一个函数被作为变量,也很常见。

什么是函数

函数就是一个 转换器
变量输入函数中,然后返回另外的变量
如果举个例子,大米 作为变量输入给 电饭煲(函数) ,最后输出的变量就是 米饭了。

注意: 『类比』本身都是不精准的,表面上更容易理解了,实质上可能背离了更远的真相。 大米 + --> 电饭煲(函数) --> 米饭 这个现实世界能理解的概念,在代码世界中,产出可未必是 米饭 这种结果变量,反而可能是 另一个电饭煲(函数)

什么是变量名

变量需要取名字,也就是 变量名

一般变量的名称是由 字母、数字(一般不以数字开头),以及 _ 组成的,但表示加减乘的 +-*/ 是运算符,不能作为变量名的。为什么呢? 原因很简单,自己想想?

关于变量名,下面是一个有趣的示例,注意: _ 、 __ 、___、____ 分别是 1、2、3、4 个下划线组成,代表不同变量,不是填空题!
比如下面这段代码是成立的 (Python 版):

_ = 1
__ = 2
___ = 3
____ = _ + __ + ___ 
print(____)

Javascript 版,可以在浏览器的 Console 中运行:

var _ = 1;
var __ = 2;
var ___ = 3;
var ____ = _ + __ + ___ ;
console.log(____)

真是令人崩溃的代码,都是什么东西!?或许在初学者眼中,技术、代码也是类似的感觉,看得懂是什么字符,却弄不清楚什么意思。其实,这里有一个误区,点透了,就是简单的东西了。
虽然看起来像鬼画符,但我们换一个变量名,就很清晰了:

a = 1
b = 2
c = 3
d = a + b + c
print(d)

注: 示例代码并无意义,仅是帮助理解『变量名』的含义,真实代码不是这么写的,不要产生误解呀!

什么是空间 (Namespace)

一个国家,是一个空间;一个省,是一个空间;一个城市,是一个空间。
对于一个变量而言,它肯定隶属于某个空间的。就仿佛定义一个人,Ta 是来自于中国、x 省、x 市、x 学校、x 班 的 xxx。

在代码世界中,也是一样。
具体到某个一个函数实现的代码片段,它位于哪个目录下的哪个文件中,就是一个它的空间

我们要有 空间 的概念,当你需要写代码来完成一个产品,代码本身也需要设计。
代码层面上越清晰,也会玄学一般影响到最终产品的用户体验层面,让其变得更友好。

作为初学者而言,代码上手后,大部分必然会一团糟,表面上一些问题解决了,实际的实现乱七八糟。
几百行、几千行的代码总量,混乱就混乱吧,并无妨;如果几万行以及更多的时候,此时的混乱就寸步难以继续前行了。

简言之, 空间,是一种 整理 的方式。
整理 是非常必要的,仍然是一个很玄学的现象: 代码是会自然腐败的,越不整理,坏得越快。


容易纠结的变量名

一个变量取什么名字,会是我们后续经常要遇到的苦恼。
如果是专业的工作中 (写代码为生),命名风格要考虑特定程序语言的特定 lint (从某种角度是代码风格的质量要求)。
反之,建立自己的风格就可以了,不然,后续涉猎到不同领域,风格的不统一,容易产生自我的分裂感……

变量名的风格

没有什么特殊情况下,我们建议使用 lower_words_here 这种风格的变量名,小写字母+下划线。你也可以通过搜索引擎了解下其它的风格,或者等未来遇到了再说,此处不做详细展开了。

一般来说,我们还应该注意下:

  1. 变量名应该清晰,区分单数名词、复数名词、被动还是主动的动词 (比如 is_deleted 和 delete 是不同的意思),看到变量名的时候,应该能明白它代表的含义;
  2. 变量名的长度,长一点没有关系,能看明白长短 更重要。

一个有趣的例子,是关于 i18n 的 (internationalization,国际化i18n首末字符i和n,18为中间的字符长度,一般为了实现多国语言自动的对应)。
我自己大概是延续了早期写 Django(一个 Web 后端框架) 的习惯,会将 _(这当然也是一个变量名) 作为 i18n 等价的变量名,算是一种语法糖。
这样处理是有很大的问题,因为_ 这个字符太常见了,比如 lower_word 中起到类似连接的作用。假设,要全局替换掉这个变量,如何匹配就是一个问题了。

internationalization 缩写为 i18n 是一个聪明的做法,但是进一步将 i18n 继续缩写成 _,看似缩短了长度,实际上,弊大于利。
去掉冗余,本身可能提高效率,另一方面,过度清除冗余,是得不偿失的。水至清则无鱼,固是此理。


附: 还是说明下跟 lower_words_here 风格配合使用的 (python 代码为例)

class MyClass():
    # 这是一个类,,每个词的首字符大写,可以衍生出很多子对象,内部可以有很多子变量、子函数
    pass

def my_func(something):
    # 这个一个函数,当做普通变量的风格
    pass

my_var = 123 # 这是普通的变量

MY_VAR = 12345 # 变量这样全大写,一般表示打死都不要改变这个变量的赋值呀

注: 对了,MY_VAR 亦或 MYVAR 本质上并不是变量真不能变,而是我们自己约定(尽量)不要去变。当然也有变量真的是无法改变的,比如 ()(python 中叫 tuple 类型) 虽然也是列表,但是它在创建的时候就不能变了,而 [](python 中叫 list 类型)则是可变可增可减的。不是重要的事情,知道怎么用的就行,规则也不绝对,特别对于脚本语言而言,一些『技术黑魔法』、『语法糖』处理下,可以毫无原则可言。或者说,我们所坚守的原则: 技术是为人类以及产品服务的工具而已,其本身并不绝对。

全局变量与局部变量

很多事情都是相对的,全局变量局部变量 也是如此。
当一个程序在运行的过程之中,它本身就是一个 空间;当一个代码文件被引用的时候,这个文件本身也是一个空间;当在 HTML 引入 Javascript 开始运行的时候,当前页面本身,也是一个空间。这些类型的空间,具有全局性,我们称之为 全局变量
其它的一些变量,一般都是存在于某个函数之内的,我们称之为 局部变量

全局也好,局部也罢,都不是严格意义上的定义。只是在告诉我们,引用、修改某个变量的时候,要理解它是隶属于哪个 空间 的, 不然就容易出错。
看起来是很简单的道理?老生常谈?又或者是很虚的概念?其实,一不小心,打基础的时候,就要开始跑偏了。
比如上文提到 Python 中的 不可变的()可变的[],当我们调用 可变的[] 的过程中,初学者容易犯一个错误,那就是读取它的同时也会去修改它,然后在其它局部再调用的时候,变量就发生了变化,结果自己搞不明白原来好好的变量怎么就改变了。为了避免这个错误,有人认为这个时候应该养成一个习惯,就是及时 复制 一个可变的[],就不用担心破坏、改变它了。对于初学者来说,能有这样解决问题的想法是很好的,但根子上不正确,不分场合,这么做基本上就是一个错误的习惯;如果还进一步建议更多初学者也养成这样的习惯,就误人子弟了……

自带空间的可变变量

『可变变量』,既然已经是变量了,怎么还有不能变的情况?不用去管它们,汉语在陈述的时候,如果要考虑到潜在的可能性,通常会带来晦涩的含义,看似更明确了,实际上更费解了。
探求本质后,也不过了了,并不复杂。

由于我们是从 脚本语言 (如 Javascript、Python) 接触编程的,脚本语言是被『修饰』过的,也在不断保持进步,它本身就是一个工具,犹如你拿到锤子,已经不用再去关注如何炼钢了。比如我们不会再特别去说面向对象编程(很耳熟吧),因为已习以为常,便不需说道了。
在程序中,每个变量(不论它是类、函数、普通变量)都有唯一的 ID (类似人类的身份证),它是基础中的基础。所谓的 ID,可以认为是一个地址,如果你后续有机会接触 C 语言类似的,就会明白,大抵就是 指针 的意思了。
关于 ID 的基础逻辑,不理解,代码照写,但是会犯错,迟早的问题。
这里特别提到自带空间可变变量 ,就是指 ID 未变,但是其 赋值 已经改变了。在脚本语言中 (比如 Python),它会很有用。比如,我们要做一个 缓存 的逻辑,通常要把缓存的数据存入某个空间,而这个空间,通常就是一个全局可调用的变量。

很抱歉,入门的一开始,就引入了有些复杂的概念;实际上应该还好,略加思考就能明白。
好了,我们来看实际的代码吧 (Python 版),a 是整数,再 +2 之后,其实已经不是原来的 a 了,而 b、c 作为字典、列表,内部数据的改变,并没有改变其 ID:

>>> a = 1
>>> id(a)
140360159345384
>>> a += 2
>>> id(a)
140360159345336
>>> b = {}
>>> id(b)
4443284096
>>> b["key"] = "value"
>>> id(b)
4443284096
>>> c = []
>>> id(c)
4443066240
>>> c.append('new_item')
>>> id(c)
4443066240