面对未知变化,如何start coding?(凑字数可绕行)

2019-01-23  本文已影响0人  奚山遇白

0.something before talk

开启正文之前,我们先来看一下两个我们程序员熟知的概念:

软件开发之瀑布模型

​ 瀑布模型严格把软件项目的开发分隔成各个开发阶段:需求分析,要件定义,基本设计,详细设计,编码,单元测试,系统测试,压力测试等。使用里程碑的方式,严格定义了各开发阶段的输入和输出,是一种流水线式的整体流程,不可拆分。


瀑布模型

软件开发之敏捷模式

​ 敏捷开发采用循序渐进的方法进行软件开发,把一个大项目分为多个相互联系,但也可独立运行的小项目,分别去完成,在此过程中软件一直处于可使用状态


敏捷开发流程

​ 那么从瀑布模型的严格确定产品需求规划从一而终实现产品价值,到敏捷拆分产品需求更快发现设计弊端修正方向的模式的转变说明了什么呢?说明了变化的变化。


reality

​ 鲁迅先生曾说过:唯一不变的是变化本身。【好吧,其实不是鲁迅说的,谁说的?我也不知道】对于软件开发来说,变化也是来的让人猝不及防。需求为什么不明确?需求又变了?所以具体需求到底是个什么鬼样子?程序员疑问三连!例如就拿我们开发SDK来说,可能在刚开始开发时仅仅需要一个简单功能,但是后期发现这个功能并不满足实际需求或者这一个功能不够,我们需要提供更多的功能支持,那么这个时候你的代码是要被全部翻盘?还是只轻松修改就能适应变化,成为了一个很重要的事情,因为这关系着你用不用加班到头秃!你说重不重要?

​ 那么面对这样未知变化的情景,我们如何开始着手码代码呢?软件开发四原则:Reusability(可重用性), Maintainability(可维护性), Scalability(可扩展性), Testability(可测试性)前三个都在向我们宣示着让你的代码以不变应万变的基本游戏规则,那么我们在开始编码时又应该怎样完美践行这些原则呢?

1.Keeping your code clean

​ 我们一定经历过接手别人的代码后看着代码挠头,或者是看着自己n久之前写的代码一脸懵的情况,引发这些焦虑的原因除了不熟悉和记忆之外还有一个更可能的原因:代码太乱了,根本无从下手。所以在保证好自己的代码质量的同时,保证代码的干净整齐也是协作开发或者为自己后期优化铺路,有效命名和适当注释提高代码可读性的很好的方法,由于各个端的代码规范不尽一致而且大家肯定也听的耳朵长茧,所以此处仅做提醒,不再赘述。

2.降低耦合

​ 高内聚低耦合是软件设计的首要准则。在着手开发一个产品的时候任何开发者都不可能预见到所有的细节设计,那么这个时候我们需要考虑的就是怎样从已有需求中划分模块-抽象模型-下沉基础-以组件插拔组装构建整个工程。

2.1适当使用分类

2.1.1分类简介

Category的主要作用是它可以在不改变原来类的基础上,为类动态的添加方法。分类的使用应该注意以下特点:

1.分类中只能增加方法,不能增加属性
2.在分类方法中可以访问原来类的.h文件中声明的属性和方法
3.分类可以重新实现原来类中的方法,但是会覆盖掉原来方法,所以在开发中尽量避免同名方法覆盖
4.方法调用的优先级:分类>原来的类>父类,如包含多个分类,则调用优先级与编译顺序有关,最后参与编译的分类优先

分类的原理是:

1.category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
2.我们平常所说的category的方法会“覆盖”掉原来类的同名方法,实际上由一可知:这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法即返回imp,也就实现了所谓的“方法覆盖”。

2.1.2为什么要用分类?

​ 一个类的不同功能模块的实现拆分开放在不同的文件内,这样可以使文件结构清晰明了,并且避免多各功能接口混杂堆叠 在一个文件造成可用性和维护性降低的情况。
例如对于不同模块的业务网络请求封装其实可以在网络基础类的基础上使用分类处理实现,这样可以有效地隔离各个业务,减少代码冗杂,可以根据具体业务查询具体分类,更加清晰易读。

2.2依赖注入

2.2.1依赖注入简介

​ 如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。例如下个例子(因swift能够更清晰的表述依赖关系,故使用swift语言展示):

class Person: NSObject {
    var cat: Cat!
    // ...
}

class Cat: NSObject {
    weak var person: Person!
    // ...
}

Person类和Cat类就有依赖关系:


依赖关系

​ 一个人拥有一只猫,一只猫属于一个人,这是一个简单关系体现,可是假如这个时候猫猫🐈需要变成狗狗🐶呢?可以预见的我们的Person类就需要作出相应改变了,如果Person类和Cat类对于多方有很深的依赖的话,那么代码改动也会变得巨大。这只是一个简单的例子,尚且或出现如此的变动,更不要说对于不可预估的复杂业务场景了,有时候被依赖的任何一方变动,迎接我们的可能就是全部代码的重头再来。面对这样的情况,我们就需要解除耦合,使用依赖注入代替强依赖。

​ 依赖注入是种实现控制反转用于解决依赖性设计模式,它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象。这样就可以解除两个对象之间的强依赖,即使一个对象发生了变化,另一个对象也可以变更少量代码甚至是不用变更代码即可适应。

2.2.2依赖注入的常用方法

​ 针对于上述Person类和Cat类的例子,我们可以将抽象类型解析为其具体实例,创建一个单独的层,通过Animal协议和PetOwner协议将Person类和Cat类间的强依赖剥离,而不是直接依赖。具体抽象结果如下所示:

protocol Animal {
    // ...
}

protocol PetOwner {
    // ...
}

class Person: PetOwner {
    var animal: Animal!
    // ...
}

class Cat: NSObject {
    var petOwner: PetOwner!
    // ...
}

1.构造函数注入

依据上述分层,我们可以在构造一个对象时实现注入:

let personA = Person(animal: Cat())
let personB = Person(animal: Dog())

2.属性注入

我们也可以使用属性进行注入:

let personA = Person()
personA.animal = Cat()

let personB = Person()
personB.animal = Dog()

3.Java中的@Inject

public class Person {
    ...
    @Inject Cat Cat;
    ...
    public Person() {
    }
}

​ 通过在字段的声明前添加 @Inject 注解进行标记,来实现依赖对象的自动注入。但是需要注意,实质上,如果你只是写了一个 @Inject 注解,Father 并不会被自动注入。你还需要使用一个依赖注入框架,并进行简单的配置。现在 Java 语言中较流行的依赖注入框架有 Google GuiceSpring 等,而在 Android 上比较流行的有 RoboGuiceDagger 等。

2.3组件化

​ 针对于复杂多变的场景,抽象分层使用组件化构建整个项目架构也是一种行之有效的方法。将公共基础组件下沉,依靠平台接口与上层具体业务模块对接。代码解耦抽象为组件是一方面,另一方面怎么把组件有序的管理起来运用于实际业务也是很大的一个问题,理想情况如下所示:


美团外卖设计图-如有侵权请联系我-我改-因为我赔不起

特别是站在我们基础架构-移动端的角度,我们不仅仅要规划整理单独的业务组件,也不仅仅是抽离出单独的功能SDK,更重要的是怎样规范化组件的配置联动,否则抽象出来的多个组件之间互相依赖,牵一发而动全身,会给开发造成更大的困难。

几个大厂的组件化之路大家可以参考下:

京东组件管理实践

京东多业务集成实践

滴滴的组件化实践与优化

美团组件化集成管理实践

美团组件化管理实践

3.增强健壮性

​ 在开发项目时我们需要考虑到代码的健壮性,在大负载和频繁操作时依旧保证顺利运行,所以需要考虑代码能否应对极端情况。

3.1注意线程管理

​ 在客户端针对频繁耗时操作,利用多处理器的优势提升响应效率,以保证应用程序的相应性能,但是对于多线程编程时需要注意避免以下常见问题,合理使用线程管理进行开发:


数据竞争

多个数据访问同一资源造成竞争,所以要针对必要数据进行加锁


死锁

加锁情况下可能会造成死锁,这时候要具体区分指定处理优先级,避免死锁


过多无用线程消耗大量内存

不要滥用线程,以免资源浪费

3.2负载均衡

负载均衡策略大多运用于后端服务支持,是采用分布式集群方案加强网络数据处理能力、提高网络的灵活性和可用性的技术方案,对于我们SDK来说可以借鉴这种思想。虽然我们客户端并没有多台服务器支撑,但是我们有多个CPU。比如针对APP启动时间过长的问题,我们没有必要把大量启动配置代码堆叠在didFinishLaunching方法中,而应该有条理性的区分各项配置具体需要启动的时机,SDK也是如此,对于初始配置和启动项应有选择性的合理安排时间,减少最初多事务压力。

小结:在面对未知变化时不要害怕编码,而应该梳理已有的需求,发挥脑洞想一想还有什么是可能后期需要的其他功能,然后按照Reusability, Maintainability,Scalability , Testability的原则去规划代码编程即可。另外对于越来越流行的敏捷开发来说,并不意味着快速开始编码,而是需要关注像上述我们提出的注意点,敏捷开发实际非常重视“设计”,并且对开发人员的设计水平提出了极高的要求,既要“持续重构”又不能“过度设计”,稍有不慎就会陷入反复推翻已有代码的窘境,善于思考,磨刀不误砍柴工。

参考:

阿里玄难:面向不确定性的软件设计几点思考

Category 特性在 iOS 组件化中的应用与管控

Dependency Injection on iOS 

Keeping your code clean

负载均衡算法及手段

上一篇下一篇

猜你喜欢

热点阅读