我为 Windows 10 修复了一个 bug
本文讲述了一名开发者为 Windows 计算器应用修复 bug 的经历。
据这名开发者(下用 Peter 代称)介绍,他某日在 Reddit 闲逛时,一个位于 Windows 10 子版块下的帖子引起了他的注意。帖子内容如下:
和大家一样,在计算两个日期之间的相隔天数时,Peter 也发现了关于周数的描述明显是错误的,如此大的数值看起来应该是上溢或者下溢之类的问题,要不就是差一错误(off-by-one)等常见的逻辑错误。
本着对这个 bug 的好奇心,再加上 Windows 10 计算器是开源项目,Peter 认为解决这个问题应该不会太复杂,所以他希望亲自找到 bug 并进行修复。
他先在自己的电脑上测试看是否能复现,按照帖子的示例,在测试 7.31-12.31 的间隔天数时,计算器返回的结果是正确的 —— “5个月”。接着 Peter 稍微改了一下日期,改成 7.31-12.30 时,bug 复现了,计算器显示的值为:“5 months, 613566756 weeks, 3 days”,这明显是错误的。
确定了 bug 的存在,Peter 决定从 Windows 计算器的 GitHub 仓库下载源码来研究一番。从 repo 把源码下载到本地后,由于在 IDE 运行 Windows 计算器项目需要 UWP workload,所以 Peter 还为 Visual Studio 添加了 UWP workload。不过 Peter 表示搭建开发环境也十分顺利,修 bug 第一步至此完成。
接着 Peter 打开了解决方案文件(solution file),查看 “Calculator” 项目,并搜索看似相关的任何文件。他找到了界面文件DateCalculator.xaml
,接着从相关的文件DateDiff_FromDate
追踪到了DateCalculatorViewModel.cpp
,最后找到DateCalculator.cpp
。
然后 Peter 开始设置断点并观察相关变量的变化,他发现 final 变量DateDifference
的值有误。因此他判断这个 bug 不是由转换为字符串存在错误而导致的,而是实实在在的计算错误。
既然计算存在问题,那就看看它的计算逻辑是如何实现的。
Windows 计算器对间隔日期的计算逻辑用伪代码表示如下:
DateDifference calculate_difference(start_date, end_date) { uint[] diff_types = [year, month, week, day] uint[] typical_days_in_type = [365, 31, 7, 1] uint[] calculated_difference = [0, 0, 0, 0] date temp_pivot_date date pivot_date = start_date uint days_diff = calculate_days_difference(start_date, end_date) for(type in differenceTypes) { temp_pivot_date = pivot_date uint current_guess = days_diff /typicalDaysInType[type] if(current_guess !=0) pivot_date = advance_date_by(pivot_date, type, current_guess) int diff_remaining bool best_guess_hit = false do{ diff_remaining = calculate_days_difference(pivot_date, end_date) if(diff_remaining < 0) { // pivotDate has gone over the end date; start from the beginning of this unit current_guess = current_guess - 1 pivot_date = temp_pivot_date pivot_date = advance_date_by(pivot_date, type, current_guess) best_guess_hit = true } else if(diff_remaining > 0) { // pivot_date is still below the end date if(best_guess_hit) break; current_guess = current_guess + 1 pivot_date = advance_date_by(pivot_date, type, 1) } } while(diff_remaining!=0) temp_pivot_date = advance_date_by(temp_pivot_date, type, current_guess) pivot_date = temp_pivot_date calculated_difference[type] = current_guess days_diff = calculate_days_difference(pivot_date, end_date) } calculcated_difference[day] = days_diff return calculcated_difference }
上面的代码主要做了这些事:先算出相差的年数、然后计算相差的月数、接着计算相差的周数、最后计算相差的天数。
Peter 表示这看起来很正常,他没发现其中的逻辑存在错误。
问题正是在于此,写这段代码的人以为代码会按预料中执行:
date = advance_date_by(date, month, somenumber) date = advance_date_by(date, month, 1)
逐一运行后如下:
date = advance_date_by(date, month, somenumber + 1)
常见情况下的确如此。
但问题在于:“如果起始日期为某月的第 31 天,结束日期所在的月份只有 30 天,该以哪天作为结束的标志?”对于 Windows.Globalization.Calendar.AddMonths(Int32) 来说,它的答案显然是“在第 30 天”。
具体而言,这就意味着:
“July 31st + 4 Months = November 30th”
“November 30th + 1 Month = December 30th”
然而实际情况是:
“July 31st + 5 Months = December 31st”
这就引起了“差一错误”。逐步调用Window::Globalization::Calendar::AddMonths
会导致GetDifferenceInDays
出现负值,然后将其分配给无符号变量daysDiff
,经过后面的循环迭代后,daysDiff
会将这个负值变为更大的数字。
接着 Peter 在 Windows 计算器的 GitHub 仓库提交了一个 PR 以进行最小化“修复”。
Peter 为修复加上了引号,是因为它最后计算出的结果如下:
Peter 表示,如果各位认可“7月31日+ 4个月= 11月30日”这样的结果,他认为这在技术上是正确的。虽然完整的结果不符合大众对日期间隔天数的阅读习惯,但至少不会出错。
不过这件事中,最令人深刻的是微软最后合并了 Peter 提交的 PR 以修复这个问题。
这说明微软的开源项目不仅仅是将代码托管在 GitHub 而已,而是会听取来自社区用户的建议和改进。
那么问题来了,如果是你,你会怎样解决这个错误呢?
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
每日一博 | 浅谈 Redis 五种数据结构的底层原理
概念 Redis作为一个开源的用C编写的非关系型数据库,基于优秀的CRUD效率,常用于软件系统的缓存,其本身提供了以下五种数据格式: string:字符串 list:列表 hash:散列表 set:无序集合 zset:有序集合 接下来我们就要针对这五种数据结构,来分析其底层的结构 这里选用的版本是redis-5.0.4,所以可能有很多地方和如今网络上的其他博文不太一致,不同的地方我会在文中指出 string 因为redis使用c语言开发,所以自然没有java和c++的那些字符串类库,在redis中,其自己定义了一种字符串格式,叫做SDS(Simple Dynamic String),即简单动态字符串 这个结构定义在sds.h中: typedef char *sds; 但是这个sds类型仅作为参数和返回值使用,并不是真正用于操作的类型,真正核心的部分是下面的这些类: struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; char buf[]; }; struct __attribute__ ((__packe...
- 下一篇
Oracle Dyn DNS 服务将在 2020 年关闭
根据slashdot网站报道,Oracle Dyn DNS 服务 将在 2020 年 5 月 31 日年关闭。 下面是 Oracle 向 Dyn 服务的客户发送的电子邮件 邮件的大概意思是,从 Oracle 在 2016 年收购 Dyn 以来,项目团队就一直在将 Dyn 的产品和网络集成到 Oracle 云基础设施中,随着 Oracle 云基础设施升级的完成,免费标准的 DNS 服务就将关闭,提供增强的付费订阅版本。 Oracle Dyn DNS 服务目前不再支持以下功能:Webhop (HTTP重定向)、动态 DNS、向外部命名服务器的区域传输和 DNSSEC。
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS关闭SELinux安全模块