每周完成一个 ARTS: 至少做一个 leetcode 的算法题、阅读并点评至少一篇英文技术文章、学习至少一个技术技巧、分享一篇有观点和思考的技术文章。(也就是 Algorithm、Review、Tips、Share 简称 ARTS)
Algorithm
这周写一道关于链表的简单难度的题目 https://leetcode.cn/problems/middle-of-the-linked-list/description/

咱们这里用到两种不同的办法进行遍历:
1、先拿到这个 Node 的 length 的长度,之后根据这个长度进行循环
2、使用两个指针,也就是快慢指针进行解决
第一种办法:
java
public class Solution {
public ListNode middleNode(ListNode head) {
int length = 0;
// 这里需要存储这个 cur
ListNode cur = head;
while (cur != null) {
length++;
cur = cur.next;
}
for (int i = 0; i < length / 2; i++) {
head = head.next;
}
return head;
}
}
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
ListNode() {
}
}
第二种办法:
原理:这个原理是因为慢指针永远都走快指针的一般,这样快指针走完之后,慢指针的位置刚好就是我们需要的位置
注意事项:
- 注意快指针的边界条件,当 fast == null 或者 fast.next == null 的时候都说明一点,就是到对应的位置了,具体可以看下面的图片
- 情况是 fast.next == null

- 情况是 fast == null

- 这个 while 循环的时候需要注意是判断 fast 如果判断 slow 会出现 NPE
java
public class Solution {
public ListNode middleNode(ListNode head) {
// 快慢指针解决这个问题
ListNode slow = head;
ListNode fast = head;
while (fast != null) {
if (fast.next != null) {
fast = fast.next.next;
} else {
return slow;
}
slow = slow.next;
}
return slow;
}
}
Review
文章:A database is a store for global variables
Sean Goedecke 从一个矛盾出发:编程时经常被告诫少用全局变量,但数据库同样保存着程序各处可以共享和修改的状态。为什么前者被视为设计问题,后者却很常见?
作者认为,两者都有隐式传递数据和耦合不同模块的问题。一处代码修改记录,另一处代码依赖它的值,双方即使没有直接调用,也会相互影响。事务可以保证一组修改的原子性,却不能消除这种依赖。
作者更认可的解释是:数据库访问通常更显式,而且使用方式有约定。查询调用让共享状态的读写更容易被发现,团队也通常只用数据库保存特定用途的数据。但这些优势依赖使用方式,隐藏查询的抽象也可能削弱它们。
因此,作者没有建议大量使用全局变量,而是提醒读者:设计经验需要能解释原因,不能仅凭「最佳实践」判断方案好坏。
沿着这个观点审视代码,可以把注意力放到共享状态的读写责任上:哪些模块有权修改,读取方依赖什么条件,修改后会影响哪些流程?这些问题比变量存在哪里更能帮助判断耦合是否可控。
进一步说,数据库约束和业务规则也需要区分。字段类型、非空约束能够限制数据形态,但业务状态是否允许转换、多个写入方是否遵守相同规则,仍然需要在应用设计中明确。审查时可以沿着一条状态变化查找全部读写位置,检查依赖是否清楚,再决定是否需要收拢写入入口或补充约束。
Tips
让 AI 写代码时,除了说清楚要什么功能,还要讲明实际规模、任务边界和维护成本,不然它会替还没出现的需求写代码。
膨胀来源
1、按想象的规模做优化。还没遇到瓶颈,就先加了缓存、批处理和复杂调度。
2、一个设计选择带出一串代码。换个方案可能直接去掉一整组依赖,这比逐行删代码收益大。
3、状态和抽象越分越细。只有对应不同业务行为的区别才值得保留。
4、降级越叠越多。最后失败处理比正常流程还复杂。要不要降级,看业务能不能接受失败。
5、辅助代码没有依据。指标、配置项、内部校验和测试接口,每一项都要说得出用途。
下任务时的约束
1、给出实际规模和代码预算,比如数据量、并发量、性能要求和大致代码量。
2、先审方案再写代码。让 AI 先说明核心流程、需要哪些组件、预计多少代码、哪些暂时不做。
3、先找现有能力。项目里或标准库已经有的,就直接复用。
4、新增复杂度要有理由。每加一个状态、抽象层、配置项、降级或日志,都要对应真实需求。
5、按风险安排测试。重点覆盖核心结果、重要边界和失败处理,不要每个类都配一个测试。
Share
文章:The PHP2Shell Vulnerability Heralds Cybersecurity Hell
这篇文章借 PHP2Shell 讨论了一个变化:AI 正在加快对旧软件的安全研究,过去积累的技术债可能更快暴露出来,而维护者未必有同样充足的资源应对。
1、漏洞研究能力的提升,会改变旧问题的风险判断。
报道介绍,研究人员借助 AI,把已知的内存处理问题推进到了可执行任意代码的利用路径。这里仍然需要专家的安全知识和判断,不能据此认为 AI 已经能独立完成整个过程。
由此可以推想,「暂时没有发现利用方式」不能成为长期搁置问题的充分理由。评估历史缺陷时,需要随研究工具和攻击能力的变化重新判断风险。
2、发现问题和安全修复问题,承担的工作不同。
文章引用研究者的观点:修复既要封住攻击路径,也要保证原有功能不被破坏。这种不对称意味着,漏洞发现提速之后,修复和验证可能承受更大压力。
PHP 基金会的事件说明进一步指出,修复曾受兼容性、性能、扩展生态和决策机制的制约。工具也带来了更多无效、重复或无法复现的报告,需要维护者逐一处理。提高安全能力,需要同时考虑报告质量、修复能力和发布协调。
3、防御端也需要工具和持续投入。
报道讨论了为开源维护者提供资金、模型资源,以及用形式化验证辅助检查补丁的方向。这些是改善修复效率的可能路径,不能直接理解为漏洞修复已经可以全面自动化。
对依赖开源软件的组织而言,可以据此检查自身的维护安排:是否清楚关键依赖由谁负责,是否有安全更新和回归验证的流程,是否能持续支持所依赖的项目。引入扫描工具之后,还需要有人判断报告、推进修复,并把补丁部署到实际系统中。