现代软件开发方法
SaaS
- SaaS通过运行在客户端设备上的瘦程序(如:浏览器),访问在Internet上以服务形式提供的软件和数据
- 无需安装,不用担心硬件能力,OS
- 无需担心数据丢失(数据远程存储)
- 便于团队与相同数据的交互
- 如果数据很大或频繁更改,在中心站点保存一份副本更简单
- 软件单一拷贝,单一的硬件/OS环境
- 开发人员没有兼容性问题
- 只需对1%的用户beta测试新功能
- 单一拷贝=>简化了开发人员的升级任务
- 持续获得用户的反馈成为可能
对基础设施需求:通讯、可扩展性和弹性、可靠性
错误:
如果使用相同类型的硬件和软件,私有数据中心的成本 可以与仓库规模的计算机相媲美
经典的软件体系结构模式/风格
- 分层架构
- 主从架构
- 管道和过滤器架构
- 点对点架构
- 事件总线架构
- 模型-视图-控制器架构
新的适应云的特点的微服务架构
不同的软件体系结构之间并不互斥,可以在同一个软件系统中结合使用
Ajax: web2.0 Http response 包含data,浏览器将response data 交由一个特别的JS函数处理,JS函数可以使用数据修改页面上展示的内容
- Web是独立、可组合的服务集合:Service oriented architecture (SOA)
SOA: 所有组件都设计为服务,以及服务可以组合的软件架构
API需要指定:
- Caller如何识别并定位被调用的函数
- 如何传递必须的/可选的参数
- Caller如何接收返回值
- Caller端出现错误时如何应对?
三层架构:
(a) 表⽰层, (b) 逻辑层, (c) 持久层
Web上的SaaS应用中,控制器动作和视图内容使用 HTTP传输
不正确的说法是:
- 所有MVC应用都有一个“客户端”部分(如Web浏览器) 和一个“云”部分(如云上的Rails应用程序)。
Active Record: 每个模型都知道如何使用通用机制进行CRUD操作 ActiveRecord 是一种Object Relational Mapping (ORM)的实现
- 适用于业务逻辑比较简单,类基本上和数据库中的表一一对应;
- 当发生跨表的操作时, 往往会配合使用事务脚本,把跨表事务提升到事务脚本中;
- 简单, 直观。 一个类就包括了数据访问和业务逻辑
缺点:领域类和表一对一匹配,随着把领域逻辑放入更小的类而失效;关系数据库无法处理继承,因此使用策略模式等面向对象模式非常困难
DataMapper将单独的映射器与每个模型关联起来,可以使用关系型数据库
js创建对象要用new
ajax:在不重新加载页面的情况下与服务器异步地通信,如输入提示
需求分析
BDD行为驱动设计
需求以用户故事记录
SMART故事:
- Specific–明确
- Measurable-可度量
- Achievable–可达成 (理想情况下,在一个迭 代中实现)
- Relevant–相关性 (“5个为什么”)
- Timeboxed-时间框 (知道什么时候放弃)
SM:每个场景可测试
低保真(Lo-Fi)
将用户故事划分为更简单的故事, 组成史诗(epic)
Cucumber 小结
- 新特征=>特征的UI,编写新的步骤定义,甚至在Cucumber能使步骤变绿之前就写新方法
- 通常先让成功路径通过
- Background可以让同一特征的场景更加精炼
- BDD/Cucumber测试系统实现后的行为;
P&D中的三明治集成旨在减少构造存根-stub的工作量,同时尝试尽早获得通用性功能
经理将SRS划分为编程单元
开发人员编写单元代码
开发人员执行单元测试
独立的质量保证(QA)团队进行更高 级别的测试: – 模块、集成、系统、验收测试
自顶向下集成:构建存根让应用运转
自底向上:直到所有代码编写和集成完成才能运转
三明治
形式化方法从形式规格说明开始,并证明程序行为符 合规格说明。
软件维护
code review
正式到非正式 桌面检查、代码走查、代码审查
缺陷检查表:
- 编程规范
- OOP
- 性能
- 资源释放处理
- 程序流程
- 线程安全
- 数据库处理
- 通讯
- 对象处理
- 异常处理
- 方法
- 安全
- 其他(日志、配置)
软件维护可以分为纠错性维护、适应性维护、改善性 维护和预防性维护四类
对软件可维护性影响的主要因素有:
可理解性(understandability) 可测试性(testability) 可修改性(modifiability) 可移植性(portability)
代码异味(Code Smell)
SOFA通常捕获那些表示代码异味的症状:
- 它是短的吗?(Short)
- 它只做一件事吗? (One)
- 它有不多的参数吗?(Few)
- 它有一致的抽象级别吗?(Abstraction)
最重要做一件事(Do One thing)
方法级重构的主要目标是提高代码的结构质量、可读性和可维护性,包括减少复杂性、消除代码异味以及提高可测试性。消除错误通常是通过测试和调试来实现,而不是通过重构来解决。
发散式变化指的是“一个类受多个外界变化的影响”,其基本思想是把相对 不变的和相对变化相隔离,即封装变化。
霰弹式修改指的是“一种变化引发多个类的修改”,其基本思想是将变化率 和变化内容相似的状态和行为放在同一个类中,即集中变化。
数据泥潭:
- 同样的两至三项数据频繁地一起出现在类和参数表中。
- 代码声明了某些字段,并声明了处理这些字段的方法,然后又声明了更多的字段和更多的方法,如此继续。
- 各组字段名以类似的子串开头或结束。
ppt 11有重构例子
比较 P&D 和 Scrum,P&D 项目经理同时担任 Scrum Master 和产品负责人:
- 这是不正确的。在传统的 P&D(计划驱动)方法中,没有 Scrum Master 和产品负责人的角色。通常只有一个项目经理负责监督项目。
团队应尽一切可能避免团队成员之间的冲突:
- 这不一定正确。虽然管理冲突是重要的,但避免所有冲突可能会导致问题得不到解决,阻碍团队进展。
P&D 可以拥有比 Scrum 更大的团队,团队成员直接向项目经理汇报:
- 这是正确的。传统的 P&D 方法通常涉及更大的团队,具有层级结构,团队成员直接向项目经理汇报。
由于研究表明 84%-90% 的项目按时并在预算内完成,P&D 经理可以自信地向客户承诺在约定的成本和日期内交付一组功能:
- 这是不正确的。尽管有研究,但许多项目仍然面临按时和按预算完成的挑战。过度承诺可能会导致不切实际的期望和潜在的项目失败。
Intended to improve the quality of the software product using the wisdom of the attendees:
- 这是正确的。会议和评审通常旨在利用与会者的集体智慧来提高软件产品的质量。
They result in technical information exchange and can be highly educational for junior people:
- 这是正确的。这样的会议可以促进技术信息的交流,并为初级团队成员提供学习机会。
Can be beneficial to both presenters and attendees:
- 这是正确的。演示者和与会者都可以获得见解和反馈,使整个过程对所有人都有益。
The A’s in SAMOSA stands for Agenda and Action item, which are optional pieces of good meetings:
- 这是错误的。根据 SAMOSAS 的指南,议程和行动项并不是可选的,而是必需的。议程需要提前创建,没有议程就不开会;会议结束时需要明确行动项,以便每个人都知道会议的结果和接下来的任务。
DevOps
服务水平目标(Service Level Objectives,SLO)
缓存整个动作的输出 ‒ 页面缓存:绕过(旁路)控制器动作 - caches_page :index ‒ 动作缓存:会调用控制器,运行过滤器
- 视图的片段缓存:缓存由渲染页面的一部分(例如.partial)产生的HTML
SSL能做什么,不能做什么
- 向浏览器确保bob.com是合法的
- 防止窃听者读(或破坏)浏览器和bob.com之间流量
- 给服务器增加了额外的工作
不能:
‒ 向服务器确定用户是谁 ‒ 说明敏感数据到达服务器后发生了什么 ‒ 说明服务器是否容易受到其他服务器的攻击 ‒ 如果服务器上存在恶意软件,保护浏览器免受破坏
设计模式
单一职责原则(SRP):一个类只负责一个功能或职责
模板方法模式&策略模式
- 模板方法:一组步骤相同,但是步骤实现不同
- 继承: 子类重载了抽象的“步骤”方法
- 策略: 任务是一样的,但是有很多途径去实现
- 组合: 组件类实现整个任务
OmniAuth是策略模式的一个示例
里氏替换原则有至少有两种含义
- 里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
- 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。
子类方法不能违背父类方法对输入输出异常的约定
- 前置条件不能被加强
前置条件即输入参数是不能被加强的,就像上面Cache的示例,Redis子类对输入参数Key的要求进行了加强,此时在调用处替换父类对象为子类对象就可能引发异常。
也就是说,子类对输入的数据的校验比父类更加严格,那子类的设计就违背了里式替换原则。
- 后置条件不能被削弱
后置条件即输出,假设我们的父类方法约定输出参数要大于0,调用父类方法的程序根据约定对输出参数进行了大于0的验证。而子类在实现的时候却输出了小于等于0的值。此时子类的涉及就违背了里氏替换原则
- 不能违背对异常的约定
在父类中,某个函数约定,只会抛出 ArgumentNullException 异常, 那子类的设计实现中只允许抛出 ArgumentNullException 异常,任何其他异常的抛出,都会导致子类违背里式替换原则。
子类方法不能违背父类方法定义的功能
子类必须完全实现父类的抽象方法
依赖注入
通过将依赖关系的创建和维护责任转移到外部容器中,使得类不需要自己实例化依赖对象,而是由外部容器动态地注入依赖。
Languages
Ruby
- 局部变量(Local variables):在 Ruby 中,局部变量以小写字母或下划线
_
开头,而不是以$
开头。以$
开头的是全局变量(Global variables)。 - 实例变量(Instance variables):实例变量以
@
开头。这些变量是对象实例专有的,不能被其他对象访问。 - 变量(Class variables):类变量以
@@
开头。这些变量在类的所有实例之间共享,属于类而不是某个具体的对象实例。
Questions
根据依赖注入原则(Dependency Injection Principle, DIP),我们应该将高层模块依赖于抽象而非具体实现,以提高代码的测试性、可维护性和模块化程度。
行为模式:责任链、命令、迭代器、中介者、备忘录、观察者、状态、策略、模板方法、访问者
- lambda 是 Proc 对象的特殊类型,但它不是 Proc 对象。
- proc 和 lambda 都会检查参数数量,如果参数数量不匹配会抛出错误。
- proc 和 lambda 对 return 关键字的含义不同。在 lambda 中,return 会从 lambda 中返回,而在 proc 中,return 会从包含 proc 的方法中返回,而不是从 proc 本身返回。