技术

Python实践 下一个平台Agent 激发LLM涌现——提示工程 LLM微调理论及实践 大佬沉思 LLM外挂知识库 LLMOps 多模态LLM Python一些比较有意思的库 LLM部分技术源码学习 LangChain源码学习 通用分布式计算引擎Ray Python并发 go依赖注入 go collection gc的基本原理 golang性能分析及优化 数据湖 高性能计算与存储 Linux2.1.13网络源代码学习 《大数据经典论文解读》 三驾马车学习 Spark 内存管理及调优 Yarn学习 从Spark部署模式开始讲源码分析 容器狂占内存资源怎么办? 多角度理解一致性 golang io使用及优化模式 Flink学习 c++学习 学习ebpf go设计哲学 ceph学习 学习mesh kvm虚拟化 学习MQ go编译器以及defer实现 学习go 为什么要有堆栈 汇编语言 计算机组成原理 运行时和库 Prometheus client mysql 事务 mysql 事务的隔离级别 mysql 索引 坏味道 学习分布式 学习网络 学习Linux go堆内存分配 golang 系统调用与阻塞处理 Goroutine 调度过程 重新认识cpu mosn有的没的 负载均衡泛谈 单元测试的新解读 《Redis核心技术与实现》笔记 《Prometheus监控实战》笔记 Prometheus 告警学习 calico源码分析 对容器云平台的理解 Prometheus 源码分析 并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go channel codereview gc分析 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes扩缩容 神经网络模型优化 直觉上理解深度学习 如何学习机器学习 TIDB源码分析 什么是云原生 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 共识算法 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全访问机制 jvm crash分析 Prometheus 学习 Kubernetes监控 容器日志采集 Kubernetes 控制器模型 容器狂占资源怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes类型系统 源码分析体会 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 jib源码分析之细节 线程排队 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI——容器网络是如何打通的 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 精简代码的利器——lombok 学习 《深入剖析kubernetes》笔记 编程语言那些事儿 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 如何分发计算 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 calico学习 AQS——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记 log4j学习 为什么netty比较难懂? 递归、回溯、动态规划 apollo client源码分析及看待面向对象设计 学习并发 docker运行java项目的常见问题 OpenTSDB 入门 spring事务小结 分布式事务 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型和jvm内存布局 java exception Linux IO学习 netty内存管理 测试环境docker化实践 netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker namespace和cgroup zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 深度学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM执行 git maven/ant/gradle/make使用 再看tcp kv系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 Go基础 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

架构

大模型推理服务框架 模型服务化(未完成) 大模型RHLF 大模型训练 大模型推理 从Attention到Transformer k8s设备管理 LLM工具栈 ddd从理念到代码 如何应用LLM 小鼠如何驾驭大象(LLM)? 多类型负载协调员Koordinator controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 容器和CPU那些事儿 kubevela源码分析 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群管理 AutoML和AutoDL 特征平台 实时训练 分布式链路追踪 helm tensorflow原理——python层分析 如何学习tensorflow 数据并行——allreduce 数据并行——ps 机器学习中的python调用c 机器学习训练框架概述 embedding的原理及实践 tensornet源码分析 大模型训练和推理 X的生成——特征工程 tvm tensorflow原理——core层分析 模型演变 《深度学习推荐系统实战》笔记 keras 和 Estimator tensorflow分布式训练 分布式训练的一些问题 基于Volcano的弹性训练 图神经网络 pytorch弹性分布式训练 从混部到统一调度 从RNN到Attention pytorch分布式训练 CNN 《动手学深度学习》笔记 pytorch与线性回归 多活 volcano特性源码分析 推理服务 kubebuilder 学习 mpi 学习pytorch client-go学习 tensorflow学习 提高gpu 利用率 GPU与容器的结合 GPU入门 AI云平台梳理 tf-operator源码分析 k8s批处理调度/Job调度 喜马拉雅容器化实践 Kubernetes 实践 学习rpc BFF openkruise学习 可观察性和监控系统 基于Kubernetes选主及应用 《许式伟的架构课》笔记 Admission Controller 与 Admission Webhook 发布平台系统设计 k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 深入controller openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go informer源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? 《软件设计之美》笔记 mecha 架构学习 Kubernetes events学习及应用 CRI 资源调度泛谈 业务系统设计原则 grpc学习 元编程 以应用为中心 istio学习 下一代微服务Service Mesh 《实现领域驱动设计》笔记 概率论 serverless 泛谈 《架构整洁之道》笔记 处理复杂性 那些年追过的并发 服务器端编程 网络通信协议 架构大杂烩 如何学习架构 《反应式设计模式》笔记 项目的演化特点 反应式架构摸索 函数式编程的设计模式 服务化 ddd反模式——CRUD的败笔 研发效能平台 重新看面向对象设计 业务系统设计的一些体会 函数式编程 《左耳听风》笔记 业务程序猿眼中的微服务管理 DDD实践——CQRS 项目隔离——案例研究 《编程的本质》笔记 系统故障排查汇总及教训 平台支持类系统的几个点 代码腾挪的艺术 abtest 系统设计汇总 《从0开始学架构》笔记 初级权限系统设计 领域驱动理念 现有上传协议分析 移动网络下的文件上传要注意的几个问题 推送系统的几个基本问题 做配置中心要想好的几个基本问题 不同层面的异步 分层那些事儿 性能问题分析 用户认证问题 资源的分配与回收——池 消息/任务队列

标签

k8s设备管理 多类型负载协调员Koordinator controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 容器和CPU那些事儿 kubevela源码分析 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群管理 helm 从混部到统一调度 volcano特性源码分析 kubebuilder 学习 client-go学习 tf-operator源码分析 k8s批处理调度/Job调度 喜马拉雅容器化实践 Kubernetes 实践 openkruise学习 基于Kubernetes选主及应用 Admission Controller 与 Admission Webhook k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 深入controller openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go informer源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? Kubernetes events学习及应用 CRI 资源调度泛谈 如何学习Kubernetes 以应用为中心 kubernetes operator kubernetes扩缩容 serverless 泛谈 什么是云原生 自定义CNI IPAM docker和k8s安全访问机制 Kubernetes监控 Kubernetes 控制器模型 Kubernetes资源调度——scheduler Kubernetes类型系统 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 Kubernetes源码分析——从kubectl开始 kubernetes yaml配置 CNI——容器网络是如何打通的 当我在说PaaS时,我在说什么 《深入剖析kubernetes》笔记 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件

换个角度看待设计模式

2016年05月04日

前言

世事纷扰,但总有几个线头。兵法多样,就那几个套路。

  设计模式 军事
战术 二十三个设计模式 三十六计
战略 4个原则 以正合以奇胜

以正合,以奇胜。以奇胜,被人们误读为奇袭得胜,还是贪巧求速的心理作怪。以奇胜的奇,不念qi,念ji,是个数学词汇,奇数、偶数的奇,古人又称为“余奇”,多余的部分,正兵安排好了,余下来的就是奇兵,关键的时候用。 简单地说,奇(ji)兵,不是出奇制胜的部队,是预备队。孙子的意思是:不要一下子把所有的牌都打完了,留一张在手上,关键时候打出去。 “奇正之变,不可胜穷也。奇正相生,如环之无端。” 奇正之间怎么相互转化呢?其实很简单,已经投入战斗的,是正兵;预备队,是奇兵。预备队投上去,就变为正兵了。正在打的部队撤下来,又变成奇兵。

分类方式

《Pattern Oriented Software Architecture》中提到,将模式分为三种类型:

  1. 体系结构模式,比如mvc
  2. 设计模式
  3. 惯用法,比如引用计数法

Design patterns were originally grouped into the categories: creational patterns, structural patterns, and behavioral patterns, and described using the concepts of delegation, aggregation, and consultation

Design patterns are composed of several sections . Of particular interest are the Structure, Participants, and Collaboration sections. These sections describe a design motif:

  1. a prototypical micro-architecture that developers copy and adapt to their particular designs to solve the recurrent problem described by the design pattern.
  2. A micro-architecture is a set of program constituents (e.g., classes, methods…) and their relationships.
  3. Developers use the design pattern by introducing in their designs this prototypical micro-architecture, which means that micro-architectures in their designs will have structure and organization similar to the chosen design motif.

http://design-patterns.readthedocs.io/zh_CN/latest/ 传统分类方式

  • 创建型模式,创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离单一职责原则,仅一个对象的创建独立出来,催生了spring的ioc,减少了代码中的创建类代码)。在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。
  • 结构型模式(Structural Pattern)描述如何将类或者对 象结合在一起形成更大的结构.结构型模式可以分为类结构型模式和对象结构型模式

    • 类结构型模式关心类的组合,一般只存在继承关系和实现关系.
    • 对象结构型模式关心类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象(也就是成员变量),然后通过该对象调用其方法。
  • 行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化(所以一般先定义好高层接口,定义好交互关系)。行为型模式不仅仅关注类和对象的结构(涉及到结构设计,但不是重点),而且重点关注它们之间的相互作用。

    行为型模式分为类行为型模式和对象行为型模式两种:

    • 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
    • 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。

根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分结构/行为型设计模式都属于对象结构/行为型设计模式。

如何写好单元测试?设计模式最重要的点还是在于解耦和复用,创建型模式将创建代码与使用代码解耦,结构型模式是将功能代码解耦,行为型模式将行为代码解耦,最终达到高内聚,松耦合的目标,设计模式体现了设计原则。我们经常说的“高内聚 松耦合”究竟什么是高内聚,什么是松耦合?

  1. 高内聚:相近功能放在同一类中,相近功能往往会被同时修改,放到同一个类中在修改时,代码更易维护(指导类本身的设计)。PS:比如将两个字段拼成一个字符串来作为map的key,“将两个字段拼成字符串”和“将字符串解析为两个字段”的方法就一定要放在一起,这样可以改一个,就知道需要改另一个。
  2. 松耦合:类与类之间的依赖关系简单清晰,一个类的代码改动不会或者很少导致依赖类的代码修改(指导类间依赖关系设计)。PS:假设一个A类里有List成员,不要在B类里 a.getList().add(element) 这样操作,A类尽量写成充血模型,否则List 换成Map了,B类恨不能也要重写。

成为通用术语

从技术演变的角度看互联网后台架构20多年前的经典著作DesignPatterns中讲过学习设计模式的意义:学习设计模式并不是要你学习一种新的技术或者编程语言,而是建立一种交流的共同语言和词汇,在方案设计时方便沟通,同时也帮助人们从更抽象的层次去分析问题本质,而不被一些实现的细枝末节所困扰。同时,当我们能把很多问题抽象出来之后,也能帮我们更深入更好地去了解现有系统。

​圣杯与银弹——没用的设计模式设计模式作为通用的术语确实可以增加不同工程师之间的沟通效率,但是降低沟通成本的前提是双方对同术语有着相同的并且正确的认识,如果双方的理解有差异,反而会制造更多的困惑。我们可以将 23 种不同的设计模式分成两部分来分析,其中一部分是单例模式、抽象工厂模式这些被广泛接受并理解的模式,另一部分是迭代子模型、命令模式和解释器模式等不容易被理解的复杂模式。从单例模式以及观察者模式的命名,我们就能猜到它们想要解决的问题,使用类似的术语也很难造成歧义,确实能够起到提高沟通效率的作用;不过,对于复杂的设计模式想要正确理解就非常困难,更不用说用来沟通了

软件设计之美

如果用数学来比喻的话,设计原则就像公理,它们是我们讨论各种问题的基础,而设计模式则是定理,它们是在特定场景下,对于经常发生的问题给出的一个可复用的解决方案。设计模式之所以能成为一个特定的解决方案,很大程度上是因为它是一种好的做法,符合软件设计原则,所以,设计原则其实是这些模式背后的东西。

学习设计模式,我们应该有一个更开阔的视角:要看到语言的局限,虽然设计模式本身并不局限于语言,但很多模式之所以出现,就是受到了语言本身的限制。比如,Visitor 模式主要是因为 C++、Java 之类的语言只支持单分发,也就是只能根据一个对象来决定调用哪个方法。而对于支持多分发的语言,Visitor 模式存在的意义就不大了。PS:比如golang有了channel,则观察者订阅者模式的意义就不大了。

Annotation 可以说是消灭设计模式的一个利器。语言本身的局限造成了一些设计模式的出现,这一点在 Java 上表现得尤其明显。随着 Java 自身的发展,随着 Java 世界的发展,有一些设计模式就越来越少的用到了。比如,Builder 模式通过 Lombok 这个库的一个 Annotation 就可以做到:

@Builder
class Student {
  private String name;
  private int age;
  ...
}

Decorator 模式也可以通过 Annotation 实现,比如,一种使用 Decorator 模式的典型场景,是实现事务,很多 Java 程序员熟悉的一种做法就是使用 Spring 的 Transactional

class Handler {
  @Transactional
  public void execute() {
    ...
  }
}

所以,我们学习设计模式除了学习标准写法的样子,还要知道,随着语言的不断发展,新的写法变成了什么样子

底层逻辑

洞察设计模式的底层逻辑

  1. 过程式就是过分强调了how,一开始就思考怎么去做,过程式思维是以自己为中心,导演了整个功能流程,自己承担了太多自己不应该承担的职责,整个设计就显得不灵活。面向对象是从对象的角度去看问题,解决问题是由各个对象协作完成,设计模式的基石就是面向对象,脱离了面向对象去谈设计模式那是耍流氓。
  2. “找到变化,封装变化”,这才是设计模式的底层逻辑

谈谈过度设计:因噎废食的陷阱

  1. 过程式编码的优点就是 “简单,好理解,易于修改”:在过程式编码中,代码片段是拥有上下文的全部信息的,设计模式则 通常上下文信息有限。 设计模式一般都会应用比较高级的语言特性,而过程式编码只需要会基本语法就可以写了。
  2. 业务逻辑不会因为过程式编码而变得更加简单,“过程式代码更加好理解”往往只是针对某个代码片段的,当我们将范围扩大到一个模块,甚至整个系统时,其中会包含大量的代码片段,如果这些代码片段全部是无模式的过程代码,理解成本会成倍增加,相似的模式则能大大降低理解成本,越大的代码库从中的收益也就越大。过程式编码只是短期比较好理解,因为没有设计模式的学习成本,但是长期来看,因为它没有固定的模式,理解成本是更高的;
  3. “设计模式” 作为对抗 “软件复杂度” 恶龙的少年,可能业务发展,缺乏演进等原因,最终自己腐坏成了新的 “恶龙”。
  4. 在互联网野蛮发展时期,大量的人才和热钱涌入,软件快速上线比一切都重要,开发效率的问题,只要招聘更多的人就能解决,哪怕在一个公司开发好几套功能一样的系统。但是随着互联网人口红利的消失,不再有充足的资源去承接业务,我们就不得不做好精耕细作的准备,扎实地累积自己的产品和技术优势,继续创造下一个十年的辉煌。

其它

许式伟:我个人不太喜欢常规意义上的 “设计模式”。或者说,我们对设计模式常规的打开方式是有问题的。理解每一个设计模式,都应该放到它想要解决的问题域来看。所以,我个人更喜欢的架构范式更多的是 “设计场景” 的总结。“设计场景” 和设计模式的区别在于它有自己清晰的问题域定义,是一个实实在在的通用子系统。是的,这些 “通用的设计场景”,才是架构师真正的武器库。如果我们架构师总能把自己所要解决的业务场景分解为多个 “通用的设计场景” 的组合,这就代表架构师有了极强的架构范式的抽象能力。而这一点,正是架构师成熟度的核心标志。

​圣杯与银弹——没用的设计模式软件系统中处处都是设计,学习设计模式无法让我们成为优秀的工程师,如果我们错误的理解了这本书的目的,以为自己学到了软件设计或者面向对象设计的精髓,那就大错特错了。软件设计的能力并不是一朝一夕就能培养出来的,它需要我们不断对代码进行思考,理解可能存在的扩展点并设计合理的抽象。PS:面向扩展点设计。​圣杯与银弹——没用的设计模式 对设计模式做了一定的批评,对“单元测试”推崇有加,提升项目单元测试覆盖率的过程会让我们思考如何写出更利于测试的代码,虽然软件工程中没有银弹,但是单元测试不是银弹可能也所差无几了。

​圣杯与银弹——没用的设计模式抽象的设计模式是从不同具体项目中总结出来的通用经验,从具体到抽象的过程相对容易,然而从抽象的模式套用到具体场景却很困难,如果没有足够的经验或者思考只会做出拙劣的设计。而且并不是居高临下的架构设计才是系统设计,每个包、方法甚至代码中的空行中都体现了作者的设计思路,抽象的理论和模式能够起到指导的作用,但是真正让设计融入系统的还是工程师的丰富经验和深入思考。

21 世纪诞生的一些编程语言与过去的编程语言有着很大的不同,不仅新的编程语言开始接收函数式编程中的一些思想和设计,上个世纪诞生的编程语言也在吸纳不同的编程范式,函数和方法成为了语言中的一等公民,我们可以直接向函数中传递函数来简化过去复杂的类关系。比如观察者模式函数式编程的设计模式

object.OnUpdate(func(u *updates) {
    ...
})

“复杂度不会消失,只会转移”,真的是这样吗?

  1. 转移复杂度是徒劳的。Kent Beck 在《实现模式》一书中提到的 Local Consequence:希望一个改动能够局部化影响。这包括你要做这个修改,需要阅读的代码,或者需要获得的知识边界,能够局限在一个局部。同时也包括你的源代码改动本身,不是一个散落到各个代码仓库,这一点那一点的片段。然而这个愿望是永远不可能达成的。如果目标是任何改动都能实现 Local Consequence ,这是绝无可能实现的。你总是要决定什么和什么放在一起的。不可能一个东西和所有其他东西都放在一起。当代码组织成方便了一类改动的时候,必然会让另外一类改动像是意大利面条似的。正因为无法让所有改动都实现 Local Consequence,所以得依赖经验去判断发生变化的频率。尽量让高频的改动变成是容易的。但是又怎么能未卜先知,提前知道什么是会高频变化的呢?如果你相信这种花大力气整一个架构,搞出一个笨拙的家伙去预测不靠谱的未来是徒劳的。那么是不是所谓的 Simple Design,堆一堆毕业生按直觉本能,996 堆砌代码逻辑其是更好的策略呢。简单,直接,有效。