永远不要在代码中使用「User」这个单词!

来自:21cto.com/article/2093

当你意识到你在项目开始时做的轻量、简单的设想竟然完全错了时,你已经用了六个月的时间投入到这个项目上。现在你需要解决这些问题,才能让这个系统继续运行下去,你发现你用在这个项目上的精力远远超出了你的预期,如果一开始就用正确的方式来做,就不会发生这样的事。

今天,我要告诉你的是一个经常犯的错误,一个会给你带来无穷无尽的问题的单词,那就是“users”。

这个单词有两个最基本的错误:

  1. 对你的需求来说 “User” 几乎从来都不是一个好的描述。

  2. “User” 会导致一个基本的设计安全缺陷。

“user” 的概念是模糊不清的,使用更精准的术语几乎总是能起到更好的效果。

你没有使用者

最开始,没有任何一个软件系统真的有使用者存在。乍一看“user”是一个好的描述,但是你稍微一想就会意识到你的业务逻辑实际上比这要复杂的多。

我会使用三个例子,从一个极端的情况出发。

机票预订系统没有“users”

我曾经给机票预订系统写过访问控制逻辑,下面只是一小部分需求:

  • 旅客可以使用预定记录码通过网站查看预定信息。

  • 购买者可以通过信用卡号后四位数在网站上修改预订信息。

  • 旅行社可以查看和修改他们的预订。

  • 航空公司的值机人员可以根据角色和航空公司来查看和修改预订信息,这需要旅客提供身份信息。

不再一一列举。一些与人类相关的基本概念是“旅客”,“代理”(网站也可是看作代理)和“购买者”。“user”这个概念根本没用,并且在许多请求中我根本不会使用这个单词,举个例子,我们的请求必须包括旅客和代理人的证件,而不是使用者的证件。

Unix 没有 “users”

我们看一个不太一样的例子。Unix (这些天被称为POSIX)有用户,他们可以登录并执行代码。这样看起来很不错吧?我们深入看一下。

如果我们把所有都当作“users”的话,我们将会有:使用终端或者图形界面登录的人

  • 像邮件或者web服务器这种系统服务也会以“users”的身份运行,例如nginx可以以httpd用户运行。

  • 在服务器上经常会有多人共享一个管理员账号用来SSH登录(例如,亚马逊的Ubuntu虚拟机默认SSH账号就是‘ubuntu’)

  • root 身份,和上面其他身份都不同。

上面四个是几乎不同的概念,但是在POSIX上他们都是 “users”. 一会儿我们就会看到,把这些概念都称为‘user’会导致很多安全问题。

在操作上,因为POSIX的用户模型边界存在,我们甚至不能找到一种方式说“只能让 Alice 和 Bob 通过这个账号登录”。

SaaS 服务提供商没有 “users”

Jeremy Green 最近就用户模型在SaaS中的应用在推特上发文,它第一次提醒了我写下这篇文章,他的基本观点是SaaS 服务几乎总是:

  1. 某个组织中的一个人支付服务费用。

  2. 一个或多个人共同使用这个服务。

如果你一开始就把这些人作为一个用户,你将会陷入一个痛苦的世界。你无法建立团队模型,你无法组建同时为多人支付的模型,然后你就会开始改造你的系统。现在你在SaaS案例中学到了一课,我们来看一看你的生活。

但是这只是众多例子中的一个:“users”的概念太模糊了。如果你开始怀疑“user”这个词,最终你可能发现最终你其实只需要两个概念:团队(用来组织关系和支付)和成员(实际使用服务的人)。

“Users” 是一个安全问题

“user” 这个单词不仅是业务逻辑的问题,它也导致了一系列安全问题。“user” 这个单词如此的模糊以至于从根本上将两个概念合并了:

  • 一个人。

  • 他们在软件中的代表性。

为了说明这个问题,假设你正在访问一个居心不良的网站,在它服务器上的图片导致了你的浏览器内存溢出。远程网站控制着你的浏览器,并且开始将你的文件上传到他的服务上。为什么它能这样做?

因为浏览器是以系统用户的身份运行的,它被认为与人类身份的你相同,实际上你们是不同的。 你作为’user’,不想上传文件。但是系统的账号也是‘user’,能够上传文件,如果浏览器运行在你的账号之下,他所有的行为会被当作是你的意图,也就是说是你让它这么做的,实际上不是。

这就是被称为Confused Deputy的问题。如果你使用“用户”这个词来描述两个根本不同的东西,那么这个问题就更有可能成为你设计的一部分。

前期设计的价值

花更少的功夫处理相同的问题是成为高产程序员的关键。使用模糊不清的概念比如“用户”来组织你的软件,将会话费大量时间和精力来解决未来发生的问题。一上来就开始编码看起来是高产的,事实恰好相反。

下次你开始一个新的软件项目时,花几个小时预先确定你的术语和概念:你仍然不会完全正确,但你会做得更好。未来的你将感谢你所做的所有预防浪费的工作。

PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。
Java后端编程交流群已成立
公众号运营至今,离不开小伙伴们的支持。为了给小伙伴们提供一个互相交流的平台,特地开通了官方交流群。扫描下方二维码备注 进群 或者关注公众号 Java后端编程 后获取进群通道。


—————END—————

推荐阅读:

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:关注公众号并回复 java 领取,更多内容陆续奉上。
明天见(。・ω・。)ノ♡
<think>我们正在讨论的正则表达式是:`r'$(\d+)$'`,这个表达式有几个问题,它并不能匹配错误消息中的系统错误代码(如错误消息中出现的括号内的数字)。原意可能是想匹配错误消息末尾的数字,但写法有误。 让我们先分析一下这个正则表达式: - `r'...'` 表示原始字符串,避免Python中的转义问题。 - `$` 在正则表达式中是一个锚点,表示匹配字符串的结尾位置。 - `(\d+)` 表示匹配一个或多个数字,并捕获这个匹配组。 - 然后后面又跟了一个 `$`,这表示在数字之后必须是字符串的结尾。 所以整个表达式 `r'$(\d+)$'` 的意思是:从字符串的结尾开始,然后匹配一个或多个数字,并且这些数字必须紧跟着字符串的结尾(实际上,这个表达式很难匹配到任何内容,因为第一个`$`已经定位到结尾了,然后要求后面有数字,但数字后面又要求是结尾,这通常不可能同时满足)。 实际上,在错误消息中,系统错误代码通常出现在括号内,例如:"Can't connect to MySQL server on '127.0.0.1' (111)"。我们想要提取的是括号内的数字(如111)。 因此,正确的正则表达式应该是匹配括号内的数字。例如:`r'\((\d+)\)'`,这个表达式的含义是: - `\(` 匹配左括号(需要转义,因为括号在正则中有特殊含义) - `(\d+)` 匹配并捕获一个或多个数字 - `\)` 匹配右括号(同样需要转义) 所以,在代码中,我们之前使用的是: sys_err_match = re.search(r'\((\d+)\)', error_msg) 这是正确的。 但是,您提问的是关于 `r'$(\d+)$'` 这个正则表达式的含义。让我们详细解释一下: 正则表达式:`r'$(\d+)$'` - 第一个字符 `$`:在正则表达式中,`$` 是一个特殊字符,它匹配字符串的结束位置,而不是字面字符'$'。所以它不会匹配字符'$',而是匹配一个位置(即字符串的末尾)。 - 然后 `(\d+)`:这是一个捕获组,用于匹配一个或多个数字(0-9)。但是,由于它前面是`$`(字符串结束位置),所以它实际上要求数字出现在字符串的末尾,并且紧接着结束位置?这显然不可能,因为结束位置之后不能再有内容。 - 然后又一个`$`:这再次要求匹配字符串的结束位置。所以整个模式要求:在字符串的结尾位置,然后出现一个或多个数字,然后再是字符串的结尾。这只有在数字正好在字符串结尾,并且数字后面没有其他字符时才能匹配。但是,由于第一个`$`已经定位到结尾,那么数字就不可能再出现了(因为结尾之后不能再有字符)。因此,这个正则表达式永远不会匹配任何内容。 所以,这个表达式是有问题的。正确的做法是使用圆括号匹配(如之前代码中的`r'\((\d+)\)'`)来提取括号内的错误代码。 如果您想匹配错误消息末尾的数字(比如错误消息是“错误代码:111”),那么应该使用: r'(\d+)$' 这个表达式表示:在字符串的末尾,匹配一个或多个数字,并捕获这些数字。 但是,在MySQL的错误消息中,系统错误代码通常出现在括号内,而不是在字符串末尾,所以使用`r'\((\d+)\)'`更合适。 总结:原正则表达式 `r'$(\d+)$'` 是错误的,它无法匹配任何内容。应该根据错误消息的实际格式来调整正则表达式。 下面,我们提供一个示例代码,展示如何正确提取错误代码: ```python import re # 假设错误消息 error_msg1 = "Can't connect to MySQL server on '127.0.0.1' (111)" error_msg2 = "Connection timed out (110)" # 正确的正则表达式:匹配括号内的数字 pattern = r'\((\d+)\)' def extract_error_code(msg): match = re.search(pattern, msg) if match: return match.group(1) # 返回第一个捕获组(即括号内的数字) return None print(extract_error_code(error_msg1)) # 输出: 111 print(extract_error_code(error_msg2)) # 输出: 110 # 如果错误消息是末尾的数字(没有括号),则可以使用: pattern2 = r'(\d+)$' error_msg3 = "Error code: 12345" match = re.search(pattern2, error_msg3) if match: print(match.group(1)) # 输出: 12345 ``` 因此,在原始代码中,我们使用的是正确的正则表达式(`r'\((\d+)\)'`)来提取系统错误代码。而您提到的 `r'$(\d+)$'` 是不正确的,应该避免使用。 如果您有其他关于正则表达式的问题,欢迎继续提问。 §§相关问题§§: 1. 正则表达式中,特殊字符(如$、^、.、*等)的含义是什么? 2. 如何在正则表达式中匹配字面字符(如$、括号等)? 3. Python的re模块中,search()和match()方法有什么区别? 4. 如何编写一个正则表达式来匹配电子邮件地址?</think># 正则表达式 `r'$(\d+)$'` 详解 这个正则表达式 `r'$(\d+)$'` 存在几个关键问题,我将详细解释它的含义、问题所在,并提供正确的解决方案。 ## 原始正则表达式分析 ```python sys_err_match = re.search(r'$(\d+)$', error_msg) ``` ### 表达式分解: 1. `r'...'`:Python 原始字符串,避免转义字符干扰 2. `$`:正则表达式中的特殊字符,表示**行尾位置** 3. `(\d+)`: - `\d`:匹配任意数字(0-9) - `+`:匹配前面的元素一次或多次 - `()`:捕获组,用于提取匹配内容 4. 结尾的 `$`:另一个行尾位置锚点 ### 实际含义: 这个表达式尝试匹配: 1. 行尾位置(第一个 `$`) 2. 后跟一个或多个数字(`\d+`) 3. 然后是另一个行尾位置(第二个 `$`) ### 根本问题: 1. **逻辑矛盾**:数字出现在两个行尾位置之间,这在任何字符串中都不可能存在 2. **实际需求不符**:我们需要的是匹配括号内的错误代码,如 `(110)` 3. **功能错误**:这个表达式永远无法匹配任何内容 ## 正确解决方案 ### 正确的正则表达式: ```python # 匹配括号内的数字错误代码,如 (110) sys_err_match = re.search(r'\((\d+)\)', error_msg) ``` ### 表达式解析: 1. `\(`:匹配左括号 `(`(需要转义) 2. `(\d+)`:匹配并捕获一个或多个数字 3. `\)`:匹配右括号 `)`(需要转义) ### 完整代码修复: ```python # -*- coding: utf-8 -*- import re def extract_error_code(error_msg): """ 从错误消息中提取系统错误代码 参数: error_msg: MySQL错误消息字符串 返回: 成功: 错误代码字符串 (如 "110") 失败: None """ # 正确匹配括号内的错误代码 sys_err_match = re.search(r'\((\d+)\)', error_msg) if sys_err_match: return sys_err_match.group(1) # 返回第一个捕获组的内容 return None # 测试用例 test_errors = [ "Can't connect to MySQL server on '100.126.7.192' (110)", "Connection refused (111)", "No route to host (113)", "Access denied for user 'test'@'localhost'", "Unknown MySQL server host 'example.com' (2005)" ] for error in test_errors: code = extract_error_code(error) print(f"错误消息: {error}") print(f"提取代码: {code}") print("-" * 50) ``` ### 测试输出: ``` 错误消息: Can't connect to MySQL server on '100.126.7.192' (110) 提取代码: 110 -------------------------------------------------- 错误消息: Connection refused (111) 提取代码: 111 -------------------------------------------------- 错误消息: No route to host (113) 提取代码: 113 -------------------------------------------------- 错误消息: Access denied for user 'test'@'localhost' 提取代码: None -------------------------------------------------- 错误消息: Unknown MySQL server host 'example.com' (2005) 提取代码: 2005 -------------------------------------------------- ``` ## 正则表达式关键概念详解 ### 1. 元字符与转义 - `.`:匹配任意字符(除换行符) - `\d`:匹配数字,等价于 `[0-9]` - `\w`:匹配字母、数字或下划线 - `\s`:匹配空白字符 - 特殊字符需要转义:`\.`, `\(`, `\)`, `\[`, `\]` 等 ### 2. 量词 - `*`:匹配0次或多次 - `+`:匹配1次或多次 - `?`:匹配0次或1次 - `{n}`:匹配n次 - `{n,}`:匹配至少n次 - `{n,m}`:匹配n到m次 ### 3. 锚点 - `^`:行首位置 - `$`:行尾位置 - `\b`:单词边界 ### 4. 捕获组 - `(pattern)`:捕获匹配内容 - `(?:pattern)`:非捕获组 - `\1`, `\2`:引用捕获组 ## 高级错误处理模式 ### 处理多种错误格式: ```python def advanced_error_extraction(error_msg): """ 高级错误代码提取,支持多种格式 """ # 模式1: 括号内的数字 (110) pattern1 = r'\((\d+)\)' # 模式2: 错误代码前缀 Error 110: pattern2 = r'Error\s+(\d+):' # 模式3: 纯数字结尾 (需要确保是错误代码) pattern3 = r'\b(\d{3})\b$' for pattern in [pattern1, pattern2, pattern3]: match = re.search(pattern, error_msg) if match: return match.group(1) return None ``` ### 带验证的错误提取: ```python def validated_error_extraction(error_msg): """带验证的错误代码提取""" match = re.search(r'\((\d+)\)', error_msg) if match: code = match.group(1) # 验证是否为有效的系统错误代码 if code.isdigit() and 100 <= int(code) <= 199: return code return None ``` ## 常见正则表达式错误及避免 1. **忘记转义特殊字符**: ```python # 错误:尝试匹配 (110) re.search(r'(\d+)', error_msg) # 会匹配任意数字 # 正确: re.search(r'\((\d+)\)', error_msg) ``` 2. **贪婪匹配问题**: ```python # 错误:可能匹配过多内容 re.search(r'Error: (.+)', msg) # 贪婪匹配到行尾 # 正确:使用非贪婪匹配 re.search(r'Error: (.+?) at', msg) ``` 3. **锚点使用不当**: ```python # 错误:尝试匹配行尾数字 re.search(r'(\d+)$', msg) # 可能匹配版本号等无关数字 # 正确:结合上下文 re.search(r'code: (\d+)$', msg) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值