ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

面向对象编程:把代码拆成小方块

面向对象编程:把代码拆成小方块 老张的良率分析脚本从最初的200行一路膨胀到2000行。所有函数堆在一个文件里改一个功能要把整个文件读一遍生怕动错一个全局变量。有天主管让他“加个CMP设备的分析”他盯着满屏代码叹了口气——要在两千行里再塞三百行谁敢保证不崩。我看了他的代码给他提了个建议把“设备”变成一个对象。FAB里炉管、光刻机、刻蚀机、CMP它们各有各的参数却共享“启动/停止/取状态/上传配方”这一套动作。用面向对象OOP先定义一个设备基类再让每种设备继承它、只写自己特有的部分。这篇文章就用这个真实场景把类、继承、封装、多态讲透。一、为什么FAB工程师需要OOPOOP不是银弹但确实是收拾复杂度的瑞士军刀。它的三个核心概念封装把数据和操作数据的函数绑在一起外部不用管内部怎么实现、继承子类复用父类的代码只补差异、多态同一接口不同设备给出不同行为。落到FAB好处立竿见影加新设备类型只写一个子类老代码一行不用动统一接口让你写报表时不用关心是CVD还是ETCH调同一个方法就行。二、核心代码①设备基类与继承# -*- coding: utf-8 -*-from abc import ABC, abstractmethodclass Equipment(ABC):所有FAB设备的基类:封装共性,定义接口def __init__(self, eq_id, chamber):self.eq_id eq_id #设备编号self.chamber chamber #腔室号self.state idle #状态: idle/run/downdef start(self):self.state runreturn f{self.eq_id}启动,腔室{self.chamber}def stop(self):self.state idlereturn f{self.eq_id}停止abstractmethoddef health_check(self):子类必须实现自己的健康检查逻辑raise NotImplementedErrorclass CvdTool(Equipment):CVD设备:关心膜厚与温度def __init__(self, eq_id, chamber, target_thk):super().__init__(eq_id, chamber)self.target_thk target_thk #目标膜厚Ådef health_check(self):# CVD特有:检查膜厚是否偏离目标return fCVD {self.eq_id}膜厚目标{self.target_thk}Å校验通过class EtchTool(Equipment):刻蚀设备:关心选择比def health_check(self):return fETCH {self.eq_id}选择比校验通过class CmpTool(Equipment):CMP设备:关心去除速率def health_check(self):return fCMP {self.eq_id}去除速率校验通过#使用:加新设备类型,基类代码一行不动fab [CvdTool(CVD-01, 1, 1000), EtchTool(ETCH-02, 3), CmpTool(CMP-05, 2)]for eq in fab:print(eq.start())print(eq.health_check())注意基类里的abstractmethod它强制每个子类都必须实现health_check否则实例化时直接报错。这等于用语法把“忘记写设备检查”这种低级错误挡在编译期。封装方面eq_id、state这些属性藏在对象内部外部只能通过start()/stop()去改不会出现“谁都可以随便改状态”的混乱。图1Equipment基类被三类设备继承共用start/stop各自实现health_check三、核心代码②封装与多态在报表里的威力封装和多态最香的地方是写“通用逻辑”时不用管具体设备。比如下面的日报函数它对CVD、ETCH、CMP一视同仁——因为都知道它们有health_check()。这就是“面向接口编程”依赖抽象不依赖细节。def daily_report(equipments):通用日报:不关心具体设备类型,只调用统一接口lines []for eq in equipments:status eq.health_check() #多态:不同设备返回不同内容lines.append(f{eq.eq_id}: {status})return \n.join(lines)#新增一台离子注入机?只要继承Equipment实现health_check即可class ImplantTool(Equipment):def health_check(self):return fIMP {self.eq_id}剂量校验通过fab2 fab [ImplantTool(IMP-09, 1)]print(daily_report(fab2)) #完全不用改daily_report再看封装带来的安全感state这个属性外部不能直接eq.staterun去改必须走start()/stop()。哪天你怀疑“为什么设备状态乱了”只要在start/stop里加一行日志所有改动状态的入口都被你抓住了。如果是全局变量你根本不知道谁在哪一行走改了它。图2封装把“状态可被随意改”的6个入口收敛到1个Bug更难藏四、什么时候不该用OOP说句公道话不是所有脚本都该上OOP。一次性数据处理、二三十行的小工具写个函数就完事硬套类反而绕。OOP适合“实体多、行为有共性、要长期维护扩展”的场景——比如设备、工单、lot这类有明显“东西”和“动作”映射的系统。老张的脚本正是卡在了“实体多要扩展”所以重构后从2000行降到了不到800行加CMP只写了30行。判断标准很简单当你发现自己在复制粘贴大段相似代码、只是参数不同那就是该抽出基类的信号当你发现改一处要通读全文件那就是该封装的信号。四、抽象基类把必须实现写进语法前面Equipment用了abstractmethod这是抽象基类ABC的能力。它的价值是强制约束任何继承Equipment的子类只要没实现health_check实例化就直接报错根本跑不起来。等于把忘记写设备检查这种低级错误从运行期提前到开发期。在FAB这种设备类型会不断新增的场景ABC是安全带。新人加一台离子注入机如果忘了写health_check测试阶段立刻报错不会等到上线后某天日报里冒出一行NoneType才被发现。约束写在语法里比写在文档里可靠一万倍。五、组合优于继承别把所有东西都塞进基类继承用错了也会变成灾难——有人喜欢把设备可能用到的所有方法都堆进基类结果基类膨胀到几百行子类一大半用不到。这时该用组合把可复用能力如日志记录、状态机、报警写成独立组件设备类在__init__里把需要的组件组合进来。比如报警能力CVD和ETCH都要但配方解析只有光刻要。与其让基类背着配方解析不如建一个Alarm组件谁需要谁组合。组合让每个类都小、都专注改动一个组件不影响其他设备。经验法则is-a用继承has-a用组合。六、设计模式在FAB的两处实战模式不是炫技是前人踩坑后的套路。第一处工厂模式Factory根据设备类型字符串CVD/ETCH返回对应子类实例主流程不用写一堆if-elif。第二处策略模式Strategy不同设备健康检查算法不同但调用接口一致把算法封装成可替换的策略对象运行时按需切换。老张重构后新增设备类型的改动量从读两千行改三百行降到写三十行新类而且老代码一行没动——这就是好结构的回报。OOP的终极目标不是看起来高级是改起来不怕。七、何时收手别为简单事上重型设计最后泼盆冷水不是所有脚本都该OOP。一次性数据处理、二三十行小工具写个函数就完事硬套类反而绕。判断标准实体多且有共性、要长期维护扩展、多人协作——满足才上OOP。否则一个简单的函数就是最高级的优雅。老张现在的选择很清醒临时分析用函数设备管理系统用OOP中间地带看会不会再长。把复杂度花在值得的地方才是工程上的成熟。八、用__slots__给对象瘦身FAB里常要维护成千上万个设备对象每个对象默认带一个__dict__存属性内存开销不小。给类加__slots__(eq_id,chamber,state)禁止动态加属性内存能省三成以上属性访问也更快。__slots__的副作用是对象不能再随意动态挂属性这反而是好事它逼你把对象属性白纸黑字写清楚避免到处eq.xxx乱塞。在设备数量大的场景这既是性能优化也是规范约束。class Equipment:__slots__ (eq_id, chamber, state) #禁止动态属性,省内存def __init__(self, eq_id, chamber):self.eq_id eq_idself.chamber chamberself.state idle九、属性装饰器property把字段变成受控接口设备状态不该被随便改但又要让人方便读。property把方法伪装成属性外部用eq.state读取内部可以做校验或计算。比如良率属性可以是实时从最近数据算出来的调用方却像读字段一样用封装和便利兼得。property的精髓是对外像字段、对内像方法在不破坏调用方式的前提下把逻辑藏进读取过程。当你哪天要给state加日志只改property内部调用方一行不用动。封装的红利在这里最明显。class CvdTool(Equipment):propertydef health(self):#读取时实时校验,调用方无感知return OK if abs(self._last_thk - self.target_thk) 5 else WARNprint(cvd.health) #像读字段,实际跑了逻辑十、实战用OOP重构成老张的脚本回到开头老张的2000行脚本。重构后设备逻辑拆成Equipment基类加各设备子类报表逻辑拆成Reporter类数据读取拆成Loader类。主流程变成loader.load()、reporter.build(equips)、exporter.save()清清爽爽不到800行。最爽的是加CMP分析写个CmpTool(Equipment)子类实现health_check主流程一行不动。老张感慨以前加功能像在毛线团里找线头现在像拼乐高——模块分明插上就好。OOP不是银弹但收拾这类复杂度它确实是瑞士军刀当你发现复制粘贴大段相似代码时就是该抽出基类的最强信号。十一、真实案例继承救了一次紧急需求有天凌晨客户要加一种新设备CmpTool的分析老张若在旧结构里得在两千行里找插入点、怕改错。用了OOP后他写一个CmpTool继承Equipment、实现health_check和专属的去除速率校验主流程reportbuilder加一行注册十五分钟交付老代码零改动、零回归。紧急需求不再要命。主管后来复盘说同样的需求旧结构要两天还容易出bug新结构十五分钟还稳。差别不在老张变厉害了是结构让加东西这件本来危险的事变成了安全的插拔。OOP回报的不是写的时候爽是改的时候不怕——而工程里大部分成本恰恰花在改上。十二、接口隔离与依赖倒置当报表要对接多种数据源CSV、MES、FDC别让Reporter直接依赖具体数据源而是定义DataSource接口CSV源和MES源都实现它。Reporter只认接口不认具体类——这叫依赖倒置。换数据源时Reporter一行不动只换实现。高层模块不依赖低层细节系统才能长期存活。from abc import ABC, abstractmethodclass DataSource(ABC):abstractmethoddef load(self): ... #子类实现:返回DataFrameclass CsvSource(DataSource): # CSV数据源def load(self): return pd.read_csv(self.path)class MesSource(DataSource): # MES数据源def load(self): return mes_client.query(self.eq)# Reporter只依赖DataSource接口,不关心具体来源reporter.run(MesSource(CVD)) #换源只换这一行接口隔离让报表逻辑稳定、数据源可替换依赖倒置让高层不依赖低层。这两条加一起是FAB数据系统能在设备换了又换、系统升了又升的现场里长期存活的原因。老张的报表能从CVD一路用到CMP靠的就是这层解耦而不是某次聪明的复制粘贴。十三、什么时候重构、什么时候忍重构不是见乱就改。我们有两个触发信号一是加新功能时改动扩散到五个以上文件二是同一个bug反复出现。满足任一个才重构否则忍着。盲目重构有概率引入新bug性价比不如先加测试兜底再小步改。忍也是一种工程判断。重构节奏讲小步快跑每次只改一个类、跑一遍测试、合入主干不攒大招。攒大招的重构往往拖到没时间然后流产或一次改完炸一片。小步、测试、常合入是重构不翻车的三件套也是OOP项目能持续演进的节奏感。回头看老张的2000行到800行不是某天灵感爆发重写是每次加功能时顺手把相关部分抽清楚半年积累成的。好结构都是长出来的不是一次设计出来的——前提是你想清楚了OOP那几个核心概念知道往哪抽、抽到什么粒度。十四、OOP与调试的衔接好结构还让调试变简单异常抛出时调用栈里清清楚楚写着Equipment.start、CvdTool.health_check你一眼知道是哪类设备的哪个方法出的错不用在两千行里大海捞针。封装把状态可被改的入口收敛到一个方法调试时只要盯那一个方法Bug藏不住。所以我们常说OOP和上一篇的调试是一体的结构清晰Bug才无处藏测试好写改动才敢做。把这两课连起来看你会发现写得稳不仅是会try-except更是从结构上就让错难发生、发生了也好找。这是工程师从写功能到写系统的跃迁。最后一句话给还在犹豫要不要学OOP的你不用一口气搞懂所有设计模式先把类、继承、封装、多态这四样用顺你写的代码就会从一团面变成一块块能拼能换的方块。剩下的等遇到真的复杂度自然就懂了。老张现在带新人第一课就是先别写代码先把实体和动作在白板上画清楚。画清楚了类图自然就出来了画不清楚说明你还没想明白——这时候写代码只会把混乱固化进系统。官网独享资源里有设备管理的完整OOP示例工程含基类、子类、测试用例照着改就是你的第一套设备管理代码。把代码拆成小方块不是为了好看是为了哪天要改的时候你知道动哪一块、不怕动错。十五、OOP与函数式的取舍OOP不是唯一答案。数据处理、流水线这类数据进结果出的任务函数式更清爽一串map、filter、groupby没有状态纠缠。我们原则是有实体有状态用OOP纯变换用函数式。两条腿走路。老张后来悟到他原来两千行脚本里一半是数据变换函数式更合适一半是设备管理OOP更合适。拆开之后前者用pandas管道后者用类代码量减半还更清楚。选范式看问题形状不看来不潮流。十六、用dataclass简化类定义Python 3.7的dataclass能大幅简化数据类加dataclass装饰器属性、初始化、__repr__自动生成不用手写__init__。设备这类主要是数据的对象用dataclass最省力既清晰又少错。from dataclasses import dataclass, fielddataclassclass CvdRecord:eq_id: str #设备编号thk: float #膜厚Åts: str #时间戳,默认值# __init__ / __repr__自动生成,不用手写dataclass不是替代OOP是OOP里数据载体类的语法糖。它让定义设备、工单、lot这类纯数据结构极简你只管加字段和类型注解样板代码编译器替你写。该用类的地方还是类该用dataclass的地方用dataclass分工明确。十七、OOP项目的包组织类多了要分包equipment/放设备类、report/放报表类、loader/放数据源、core/放抽象基类和接口。每包__init__暴露公开API内部模块藏实现。包结构即架构图新人看目录就懂系统怎么分。我们还给每个包写文档串起来import路径短而清晰。包组织好了几千行的系统也不乱改一个类的影响范围一眼能看。目录是代码给未来的自己留的地图。十八、OOP与测试的自然契合类把行为封装成方法测试就能逐方法验。Equipment.start、CvdTool.health_check各自有测试改一个不影响另一个的验证。OOP让测试粒度更细、更稳这也是为什么重构敢小步快跑——每个方法都有测试兜底。对比旧的两千行脚本改一处要通读全文才敢动现在的类改CvdTool只跑CvdTool的测试几分钟确认没破。测试成本随结构清晰而下降这是OOP隐形的复利。十九、给转OOP的新人的建议别一上来就背设计模式。先写能跑的函数等发现复制粘贴多、改一处动全身时自然就想到抽类。OOP是痛出来的不是学出来的。带着真实的复杂度去用四样基础概念够你走很远。还有类不是越小越好。一个类如果只有一个方法、没有状态那它其实就是个函数别硬套类。OOP的粒度跟着实体走不跟着想显得高级走。简单才是最难的设计。二十、收尾拆的是未来的焦虑回头看老张的故事OOP给他的不只是代码变短更是一种底气加设备不怕、改逻辑不慌、新人能接。这种底气才是工程能力真正的复利。把代码拆成小方块拆的其实是未来的焦虑。你今天分得越清明天改得越稳。官网独享资源里的设备管理OOP工程已经把本文所有类、测试、dataclass都备好clone下来跑通就是你的第一套生产级设备管理代码。别只看动手改一个子类你就真正入门了。二十一、OOP的过度设计陷阱最后提醒OOP也能过度。为未必发生的变化预设五层抽象、为三个对象引入工厂加策略加观察者结果代码比问题还复杂。抽象的代价是可读性只在变化真的发生时才值。YAGNI原则你不会需要它就先别写它。二十二、OOP与设计模式的边界设计模式是好东西但初学者最容易走火为三个对象上工厂加策略加观察者代码比问题复杂三倍。我们的原则是模式解决的是反复出现的特定问题没遇到就别预设。YAGNI是OOP里最被低估的一条。老张的体会他见过最优雅的代码往往是最朴素的——一个基类加几个子类没有一层套一层的抽象。优雅不等于复杂能让人一眼看懂的结构才是真好结构。二十三、用类型注解让OOP更稳Python是动态类型OOP大了容易传错类型。我们给方法签名加类型注解def health_check(self) - str配合mypy静态检查很多传错对象的Bug在提交前就被拦下。注解是给人和工具看的文档。类型注解不拖慢运行却大幅降低维护成本。尤其多人协作的设备管理系统注解让接口契约显式新人改代码时知道该传什么、返回什么不敢乱来。二十四、OOP项目的迁移与重构实录我们做过一次大迁移把旧的两千行脚本整体重构成OOP分三周、每周一个小目标——第一周抽Equipment基类第二周拆各设备子类第三周抽Reporter和Loader。每步都有测试兜底没出过线上事故。渐进式重构的关键是永远让系统在中间状态也能跑。别搞下周重写那基本都会流产。小步、测试、常合入老系统也能平稳长成新结构。二十五、收尾补一句OOP写到这我想说的其实和开头呼应它解决的是复杂度。当你面对的设备从一台变一百台、要维护的人从你一个变一队结构带来的不是优雅是活下去的能力。把代码拆成小方块不是为了给谁看是为了当系统长大时你还能睡安稳觉。官网独享资源里的设备管理OOP工程就是你练习这种能力的现成沙盒。面向对象不是为了显得高级而是当系统变复杂时给你一把能把混乱重新切回方块的刀。——官网独享资源www.yezhihui.cn ——
返回列表