编程之道
前言
此章节主要记录这几年编程生涯中与学习中得出来的做事经验
业务理解
从需求分析 到 概要设计,再到 详细设计(包含数据库设计)流程
业务建模--->功能建模--->单一功能--->考察实现(选取)--->代码实现
- 业务建模=>宏观上看”产品“在业务场景中的角色(职责)
业务是指已经存在,有着一定规范化的工作流程
并抽象其出流程的本质,
让我们开发的产品能够嵌入到流程之中,或者总领流程。使得我们开发的产品能够解决问题(缺陷),或带来一定的价值。
- 单一功能 : (到这里就对应着我们的视图函数)
1.记住第一点,我们编写的是网页。只要知道最终前端的数据表现形式。(如果确定了前端页面布局形式,那就绰绰有余)
跟普通函数设计设计一样,最重要的还是确定返回数据的表现形式
2.分割成更小的步骤
3. 再就考察小功能点实现方式的优缺点
例如:小功能点的实现考察
功能代码的编写
写代码其实不难,概括来说就是发现问题,解决问题。这是从功能点(解决问题)上去编程
思路:类或者方法怎样写?首先要看这个要写的类要解决什么问题。
例如:编写BookViewModel类
第一个要解决的问题,统一要返回的数据结构。
第二个,增加客户端额外所需要的数据键值对
相关经验
1. 明确好具体的最终的期望形态(最好用文字描述)和现状,明确”编写策略“,这是业务级代码的编写信条
2. 做任何产品的时候,思维都是由简到繁
写代码时表现为:从核心到扩展,扩展是尽量在配合原‘代码’的要求,一个点一个点明确地有秩序地添加。(即任何事都有一个流程,读一段代码,明白其流程,找到其可以插队修改的地方)
早期做产品的时候,一定围绕着一个核心功能点来逐步扩展开发。千万不要在开始的时候,就给自己的网站添加太多的功能,网站要有自己的核心价值,其他功能都是辅助功能
例如:很多框架看似很复杂,但是其核心原理越是非常简单的
所以说,能不能用一句话来概括自己的产品是一个很重要的点。
如果不能概括,那么证明你对自己产品定位是不清晰的
例如:去融资的时候,很多VC都会问能不能用一句话来概括自己产品的价值
3. 当我们准备下手的时候,往往最难的是从哪里开始
一般是从我们之前确定的核心功能开始,逐步的扩展和迭代。
(可以理解为,例如预期一个数据结构可以不同的对象得到显示效果是一样的,不会出现絮乱)
代码阅读之道
1.源码阅读的目的产物,要清楚到可以背的程序,第一步干什么,第二步干什么,第三步干什么
2.学习编程一定要有选择性地去深挖,而比如有些配置文件的项就没有必要去挖得这么深。
有意义的深挖如:python装饰器的高级用法
3. 看源代码解决问题是python的最好方式
4. 看源代码的时候一个最重要的原则是,不要一次性地把所有实现细节都看一遍。
5. 看源代码一定要分层地去看。第一遍要理清楚源代码的结构和线索,而不是要关注这些具体的细节
例如:阅读到is_isbn_or_key这里时,知道这个函数是做什么的就够了,没有必要进去看具体的实现细节
编程方法论
封装成函数之后,思想的分岔
刚开始编程的时候采用的是无机构无组织,按步骤组织语言。但这样会造成可读性很差、重用性差、可扩展性差三个弊端。
所以人们开始结构化:结构化第一步便是将可重用性的代码封装成函数。这一步实现了代码的重用。这一步开始便可以有两种风格:面向过程与面向对象
面向过程
基于面向过程设计程序就好比在设计一条流水线,通过把一个大问题分解成小问题,并且问题经常有时间上先后顺序之分
面向过程是一种编程思路,面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么
例如:用户输入用户名、密码--->用户验证--->欢迎界面、例子2:用户输入sql--->sql解析--->执行功能
优点:复杂的问题流程化,进而简单化
缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身
应用场景
扩展性要求不高的场景,典型案例如linux内核,git,httpd
函数式编程
融合数学函数(设计)和编程语言函数(实现),侧重于数学(先建立出一个数学模型例如y=2*x+1,然后用编程语言函数实现),优点是可大量缩减代码,缺点是可读性差。
注意的是函数式编程没有循环,循环由递归实现。并且不能用变量保存状态、不应该定义变量
面向对象
基本思想是遇到一个问题找个解决这个问题的对象。
对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,
与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。
优点:
解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
1. 编程的复杂度远高于面向过程
不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。
一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
2. 无法精准地预测最终结果
向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,而面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。
于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,
这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
应用场景:
需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
事件驱动思想
事件驱动本身就是一种编程范式,也就是本身什么都不是,就是一种写代码的思路
每一种编程思想都有一个其产生的需求
产生缘由
一般程序效果是,到底什么时候发生什么事情,开发者都是知道的。也就是说整个代码都在其掌控之间
即使与用户交互。因为你知道代码肯定会走到这一步,这一步也必将引领到那一个流程
例如:ftp客户端,在指定时机让用户输入用户吗名和密码
而事件驱动是,程序到底发生什么事情,完全是基于用户发生了什么选择(动作)。
实现原理
事件驱动编程需要依赖事件驱动模型
事件驱动模型:我触发了一个已经被注册了的事件,这个事件就会被放到一个消息队列里面去,有一个线程专门
从这个队列里面拿出来,并把相应的动作给执行
应用场景
UI编程,JavaScript,windows编程
代码设计
函数的设计
1. 函数本身就是带描述信息,恰当的函数名非常重要
2. 函数应该避免啰里八嗦的多次嵌套if else,改用if not XXX:return(这个可以使得看代码时候,集中注意力考虑主要的分支),或者使用三元表达式简化代码
3. 好代码的判断
代码占函数流程的几分之几,代码行数又占几分之几,两者应该相等
(若比重太大,即使有注释也不好。并且过多注释也不是一个很好的做法,可以通过定义‘有意义’的函数名替代注释)。
(并且如果细节暴露在视图函数当中,会强迫所有阅读源码的人都要看实现的细节,非常地不好!)
4. 需要变动的变量应该写在配置文件里,配置文件就是让我们动态改变代码参数的地方
例如:per_page = 15 # 这里应该写在配置文件里,因为需要更改时,在配置文件中更改远比在代码中更改好
5. 若要编写处理集合的数据,首先就应该编写单一的数据,使得处理集合的函数调用处理单一的
6. 不要在函数中返回元祖,应该返回字典
7. 有些函数,例如请求API数据的函数,出异常了应该返回一个固定的默认值,而不应该抛出。
8. 函数编写的场合
- ‘代办一件小事’
- 简化已有函数的参数调用,即内部处理数据至接口要求
- 简化已有函数的返回值判断
- 类中的函数则是一般是处理处理同一件事的‘多种多类’的方法,或者一连串的‘行为’
视图函数的设计
1. web开发的难点,就是把功能处理每个点需要考虑到周全,并把其转换为代码
2. 业务逻辑一定要理清楚,思路没有理清楚,代码是肯定写不出来
例如:理清楚 '书籍详情页面的赠送功能'
- 注意:只要搞清楚业务模型与模型之间的关系,业务逻辑并不复杂
3. 无论写什么web应用,视图函数的编写应该分层,隐藏实现细节
视图处理顺序一般是Form层->models层(看看是否操作能封装到模型类中,忌太长)-> viewmodel层->返回
- Form层
1.负责验证所有传到视图的参数
所有的参数验证都不会写在视图里,而是挪到验证层中,如果不这样做,视图函数中会充斥着各种if else,非常臃肿
- models层
1. 负责提供原始数据
2. 提供个体用例意义操作
3. 提供将转化数据的函数(@proproety装饰器可以有妙用)
- viewmodel层
1.负责将原始数据转变为视图数据。主要用于裁剪数据,增加数据,改变数据的值的格式
2.可以定义配合模版工作的方法
让模版处理一些格式的数据,有时候是非常麻烦的,
例如 村上春树/大调出版社/2010-9 ,这些数据有一个不存在,/就会出现两次 //
所以在viewmodel中额外定义一个函数处理是不错的选择,(注意的是join函数对拼接有奇效)
3. 注意:假如是做前后端分离,数据最好按接口格式返回过去,让前端的JavaScript处理,而如果使用视图渲染,最好在viewmodel层里进行格式处理
-Spider层
当数据不是从数据库里取出来,而是从API中获取而来,需要定义这一层。一般需要两个文件
1. requset.py 负责请求api数据并保存起来
2. store.py 负责将请求过来到数据保存到本地数据库,用作缓存
4. 有具体业务意义的代码尽量不要写在视图函数里,一般将这个代码封装成函数,划归到一个层中,一般是模型层
2. 数据是描述对象的特征,函数是描述对象的行为,(proproety装饰器可以将带有描述特征的函数表现为恰到好处)
类的设计必须满足这一条,若不满足,其实就是伪面向对象,例如全部都是静态方法的类
3. 类函数的编写
- 不建议在函数里面直接修改实例属性,而是调用后由返回结果来修改,如果硬要,则方法应该为私有方法
- 方法不应该直接调用全局对象,包括全局代理对象,因为会造成强耦合
- 内部知道细节,外部是不需要知道细节的。
比如,yushu_book[0]就不要存在了,因为这要求调用方关注类的属性而不是外部接口,正确的做法是提供一个良好的外部接口first返回回去
- 接口设计多站在调用者角度考虑
因为大多时候,我们编写代码是需要提供给别人调用的。
例如:参数不合接口规范,要在调用时转换,还是在类内部转换。
答案应该是类函数应该具备接收常出现的参数的接口,并在类部进行转换
4 .类的设计最常用的是封装已有的对象解决问题(组合)
封装,软件世界里面的一切都是由封装来构建的,没有什么是封装解决不了的问题。如果一次封装解决不了问题,那就再来一次
例如:Local对象是封装了字典做线程隔离,而LocalStack是对Local再一次封装,完成线程隔离的栈的作用
5. 当有必要的时候才引入设计模式
例如:flask上下文的设计是代理模式。
其实上下文,是一个对象。例如,应用上下文其内部封装了app,然后加入一些额外的成员变量(就是简单再封装)。但是其内部使用代理模式做成上下文
编程思想设计
1. 面向切片编程(AOP)
例如,flask不要在零散的再每一个可能出现404异常的地方try..except,而是把所有处理的代码都集中到一个地方进行处理
flask是使用装饰器来实现,定义在随机的模块中。(自己可以思考一下,要编写框架时,能不能使用装饰器实现一个)
2. 防御性编程
经典表现为配置文件设置默认值
代码文件布局
1. 视图中,不同业务模型应该分配到不同的文件里面去,例如账户业务一类,购物车一类,订单一类
2. 入口文件应该做启动服务器以及一些初始化工作
3. __init__.py应该肩负着包的一些初始化工作
4. 配置文件分层
- 应该分成两个配置文件,secure_setting.py 和setting.py
- 生产环境和开发环境差别大的写在secure_setting.py ,例如DEBUG=True
- 机密的写在secure_setting.py ,例如数据库账号密码
- secure_setting不会上传到git
5. 目录结构可以随意组织,但前提是一定要有意义
- 比如说,为什么要将全部视图函数都放在web目录下,因为web是一个蓝图文件夹
-为什么要有form,因为form要存放所有的验证层
- models存放所有的模型层
Pycharm快捷键
1.快速导入所需模块
-首先直接用就行
-鼠标放在未导入变量,然后alt+enter
2.看源码快速跳转
cammd+alt + '->' 看代码跳进去
cammd+alt + '<-' 看代码跳出来
规律和技巧
1. 数据库模型的冗余字段会导致存储数据不一致
而使用外键关联的字段是会随着被关联字段的模型对象的改变而动态改变的
所以,有记录意义的字段不要做模型的关联,而是直接使用冗余字段(但是冗余会导致数据不一致)
例如:淘宝的购买记录中,有记录了购买这件商品时的价格,这个历史价格并不会随着商家改变价格而改变。
2. 使用字典代替switch语句
异常
1.异常信息的查看方法
异常信息trade,最后一条为总结信息,看这条信息基本可以确定异常的信息,
然后从后往前看,观察触发异常的语句
2.一些异常的处理
- 循环导入
- 循环导入很容易触发异常,而异常信息不会直接指明是循环导入造成的
- 解决循环导入一个有效办法是在函数局部中导入其中一个循环的模块,这个是推荐方法。
生产环境与开发环境
开发环境和生产环境的代码一定是镜像关系,即一模一样。
格式规范