iOS项目分析及优化
iOS项目分析及优化
从代码看一个程序员的笔力
从代码的整洁度上就可以看出一个程序员的实力,规范其实就是让你养成一种良好习惯的标杆,在此面前我们应该顺从。本篇我们以OC为例,统计了一些在编写程序中需要注意的事项,共有20条,当然还有更多的规范,此处只是做个示例。
-
单页代码最好控制在800行以内,每个方法最好不要超过100行,过多建议对代码进行重构
-
相同的逻辑方法定义避免在多个地方出现,尽量将公用的类、方法抽取出来
-
删除未被使用的代码,不要大片注释未被使用的代码,确定代码不会使用,请及时删除
-
对其他项目中copy过来的代码,根据具体需要更新代码风格,及时删除未被使用的代码
-
项目中所有Group或者文件名称(图片名字等),不要使用汉字命名,尽量使用英文命名,国内特有名词可以使用拼音。
-
项目中所有Group都需要在项目目录中存在一个真实的目录,Group中的文件与真实目录中文件一一对应。
-
请在项目中写必要代码的注释
-
请多使用 #pragma mark - Mark Name 对方法进行分组 。如:
1
|
#pragma mark - **********View lifeCycle****** |
-
所有类名称以项目工程开头命名,如:“JS”(简书)。针对不同视图控制器,在末尾添加后缀,如: UIButton 后缀添加“Button"或大家皆知的简写,NSArray的变量命名为xxxArray等。
-
类、方法、属性等命名,做到见名知意,采用驼峰式命名规则。
-
根据资源类型或者所属业务逻辑对项目资源进行分组,使得整个项目结构清晰明了;整个项目保持一种代码书写风格。
-
避免在程序中直接出现常数,使用超过一次的应以宏定义的形式来替代。常数的宏定义应与它实际使用时的类型相一致。如以3.0来定义浮点类型,用3表示整型。 常量的命名应当能够表达出它的用途,并且用大写字母表示。例如:
1
|
#define PI 3.1415926 |
-
当使用条件语句编码时,不要嵌套if语句,多个返回语句也是OK。
1
2
3
4
5
6
|
- (void)testMethod { if (![testSome boolValue]) { // 不合适就返回,下面做处理 return ; } //Do something important } |
-
当方法通过引用来返回一个错误参数,判断返回值而不是错误变量。在成功的情况下,有些Apple的APIs记录垃圾值(garbage values)到错误参数(如果non-NULL),那么判断错误值会导致false负值和crash。
1
2
3
4
|
NSError *error; if (![self trySomethingWithError:&error]) { // Handle Error } |
-
当参数过长时,每个参数占用一行,以冒号对齐。如:
1
2
3
4
5
|
- (void)aboutFisrtNumber:(NSString *)oneStr withNextNumber:(NSString *)twoStr withLastNumber:(NSString *)threeStr{ // do something } |
-
一行很长的代码应该分成两行代码,下一行用两个空格隔开
1
2
|
self.productsRequest = [[JSProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; |
-
删除多余的空行,所有方法与方法之间空1行,所有代码块之间空1行。变量声明后需要空1行,如果需要分类区别,各类别之间空1行。条件、循环,选择语句,整个语句结束,需要空1行。最后一个括弧之前不空行。注释与代码之间不空行。
1
|
#pragma 与方法之间空1行 |
-
每行代码最多不得超过100个字。
-
如果类声明中包含多个protocol,每个protocol占用一行,缩进2个字符如:
-
图片命名:采用单词全拼,或者大家公认无岐义的缩写(比如:nav,bg,btn等);采用“模块+功能”命名法,模块分为公共模块、私有模块。公共模块主要包括统一的背景,导航条,标签,公共的按钮背景,公共的默认图等等;私有模块主要根据app的业务;功能模块划分,比如用户中心,消息中心等。建议背景图采用以bg作前缀,按钮背景采用btn作前缀。如:account_gray_qq@2x.png,addpic_icon_menu_disable@2x.png等。
着手一个新项目条理
在着手一个项目前先读文档(如果有文档的话)。尽管读了文档你不一定知道每一个代码的细节,但是如果你了解那个问题的话,你一定知道怎么写可以写出一个满足文档的内容。这个时候大脑里面就可以有个框架,先猜一猜,然后看代码,事半功倍。找不到好的文档,就看他的测试用例,也是有一样的功效的。因为测试都是从文档出发编写的,而不是从代码出发编写的。找不到文档和测试用例?那就直接Gank吧。
读代码要层次化、带着问题去阅读。首先整体了解这个软件是干什么?解决什么问题?包含哪些大的模块,各个模块的作用是什么,各个模块的调用关系怎么样?然后对于每一个模块,这个模块是干什么的?为什么要有这个模块?这个模块怎么实现的?最后细化到每一个包,每一个类,每一个函数方法。从上到下,一一击破每一个问题,认真去思考他这样设计、写代码的好处,因为好的软件都会满足这种从抽象->具体的原则的。
在开始读具体代码前定位好所有要读的文件,知道他们的位置和名字,设计良好的工程光看工程的目录结构和文件名就能知道个大概功能了。事实上阅读代码的难易程度70%取决于代码书写的规范程度,写乱掉的代码,大师也读不懂。之后根据你对目录结构的理解确定文件阅读的顺序(我反正都是从main函数开始读的)。你最好对设计模式有一定了解,否则你读面向对象的code时会经常无法理解code为啥要弄得这么层层嵌套。阅读代码一个最重要的提升水平的地方就是理解好的代码如何合理使用设计模式。基本的阅读起点都会选择main函数或者类的构造函数。然后把自己想象成cpu执行程序那样去阅读你的代码。遇到需要跳转函数时,不要急于跳转,以了解函数功能和输入输出为目标,读代码最忌讳的是不抓结构抓细节,只见树木不见森林,比起某个函数具体功能来说对结构的全局把握更重要。功能了解清楚后继续跳回来(这里就可以区分代码写的优不优秀,优秀的代码光看函数名字就知道功能,连跳转都不用)。结构弄清楚了,知道程序怎么跑了,source code的精华你已经读了60%了,之后根据需要再对具体函数深入分析,到这里整个代码已经被你扒光了,没什么神秘了。
阅读代码有两种模式:top-down 和 bottom-up。Top-down 模式,就是先设定一个 use case,比如说打开一个文件。然后静态跟着代码看,或者用 debugger 跟着看。每次出现函数调用的时候,把函数的执行层次纪录下来。大致如下:
1
2
3
4
|
func1( ) func2( ) func( ) func3( ) |
这种图表很随意,你可以根据自己的需要增加信息,可以把重要的「实际参数」一直标下来,画函数调用图,然后标注每个函数在干什么。不过这个图无法清楚地表明一个变量的轨迹,需要另外的图来标示变量的变化轨迹。要是想提高阅读代码的速度,归根结底要多读多写。熟悉程序的基本构成单元(例如循环、分支)的常见写法,各种lib, api的调用方式。这样阅读深层次代码不用再回头查形式参数到底指什么。这个图的基本作用是防止在阅读深层次代码时忘记总体执行层次。Top-down 模式进行到一定层次,往往会发现虽然图画了出来,但还是无法了解程序在干什么。这时需要转入 bottom-up 模式,一直深入到最底层,给能了解作用的底层函数一个一个的写文档。当然这时的文档是完全底层的观点。bottom-up的阅读方法,有时候会一头扎进去,出不来了。这种方式适合读一些比较优秀的开源项目的代码,也会很好地提高内功。然后就是不断在两个模式之间转换,不断的细化两种模式的理解。
最后,对于OC工程可以去GitHub找UIViewController-Swizzled这个库,拉下来放到项目里,他有什么用呢?他可以把每个页面的类名打出来。而且有层次结构,也就是说你只需要打开项目点点点,就知道这个App运行的顺序了。
iOS开发的细节及全局观
"好代码是廉价的",这句话没有歧义。中国的语言博大精深,其实这句话的真实含义是,优秀的代码使用起来毫不费力。通常我们对好代码的定义是优雅的使用各种设计模式,兼顾各种情况(异常或是正常),有效而且合理的使用优化算法等。很好,这在开发中帮我们做了很多事,这也是在开发中值得发扬的。但是,这难免的会增加实现的复杂性,如果对某些技巧认识不清极有可能成为一种开发的阻碍。实际上,在日常的开发中,我们大多是为了实现功能,很多的都是在写“廉价的代码”,以功能优先,先实现功能然后再根据需要在后期进行优化,这没什么不好。
其中一个误区是,我们需要从逻辑着手而不是功能,这样固然很好,但却会影响开发的效率。好的程序员和伟大的程序员之间的区别就在于伟大的程序员理解他们的模式,让代码廉价。当模式能够给你带来好处,而且为你省时时才去使用它们,如果不是这样就不要使用它们。当框架能够帮你提高开发速度时才使用它们,
在必要的时候重构,不要做一些超前性的开发。这些只是针对于日常开发,编写SDK或框架是另一套逻辑,总之就是不要抱残守缺,固守一种形态,学会因地制宜。
一个不好的现实是大多数程序员都是业务性程序员,每天重复着一样的工作,写着功能不同但形式上是一样的代码,这就是普通程序员的宿命,因此上面才会为了快速构建而选择“廉价的代码”。但是,这不是有进取精神的程序员所想要的,所以我们才会有进步,即使是一个小工也要像专家一样思考。
那么,所谓的专家们,在看一个项目时是如何思考的呢,我们下面做一些分析。
优秀的开发者会从架构的视角来看问题,一般而言,软件系统的架构(Architecture)有两个要素:
-
它是一个软件系统从整体到部分的最高层次的划分。
-
一个系统通常是由元件组成的,而这些元件如何形成、相互之间如何发生作用,则是关于这个系统本身结构的重要信息。
架构是一个约定,一个规则,一个大家都懂得遵守的共识。需要强调的是“架构因未来而存在”。架构的最终体现是一个软件,是模块化,简洁,可维护,可任意替换,人性化设计,可以把它全部打碎了重新从一个模型自由的再去组装成另一个模型。是高内聚,低耦合,既可以作为一个完整的可交付模块,也可以“打碎”重组。架构需要考虑的是扩展性,安全和性能,如此才算是合理的架构。
在构建项目时,不管采用什么方法,全局观、高度的代码审美能力、灵活使用各种设计模式一定都是贯穿其中的。首先,搞清楚业务逻辑,这决定了你的架构是否足够易用。另外,传的参数越少,耦合度相对而言就越小,你替换模块或者升级模块所花的的代价就越小。搞清楚业务之间的依赖关系,建立好模块交流规范并设计模块,关键在于建立一套统一的交流规范。推演预测一下未来可能的走向,必要时添加新的模块,记录更多的基础数据以备未来之需。软件是有生命的,多一点考虑便会多一分健壮。
好的架构需要下面几点:
-
代码整齐,分类明确,没有common,没有core
-
不用文档,或很少文档,就能让业务方上手
-
思路和方法要统一,尽量不要多元
-
没有横向依赖,万不得已不出现跨层访问
-
对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件
-
易测试,易拓展
-
保持一定量的超前性
-
接口少,接口参数少
-
高性能
架构师的第一职责是关注非功能性需求,就是对技术的全面掌控,包括TCP/IP协议,加密解密,计算机原理(增补反码),JPG码,MPEG2-3协议,逻辑电子电路,计算机编译器原理(堆、栈、队列)等。一些底层的东西,才是技术的核心。“技术人材是当下企业的第一生产力”,在处理业务时学会思考,用架构师的思想去思考,即使是一个小工也要有伟大理想。
敏捷开发
Scrum能吸引我的最大一个原因是它能迫使你敏捷开发;它能迫使你在每个Sprint结束的时候把东西都实现、发布。它不会让你做出目前用不到的多余的东西;它不会允许你在实现东西上有任何所谓“正确方式”的奢侈行为。在CMM(能力成熟度模型Capability Maturity Model的缩写,是一种侧重于软件开发过程的管理及工程能力的提高与评估的开发模型)神话崩溃以后,敏捷开发逐渐引起了人们的关注,并被寄予厚望。下面我们就来谈一谈敏捷开发相关的一些知识。
敏捷开发的起源
我们大部分人都学过瀑布开发模型,它是以文档为驱动的。因为在瀑布的整个开发过程中,开发人员根据需求文档进行开发,一切以文档为依据。敏捷开发(Agile Development)是一种以人为核心、迭代、循序渐进的开发方法,是一种软件开发的流程,它会指导我们用规定的环节去一步一步完成项目的开发;而这种开发方式的主要驱动核心是人,注重的是人与人之间,面对面的交流;它只写有必要的文档,或尽量少写文档;采用的是迭代式开发。
敏捷开发提倡将一个完整的软件版本划分为多个迭代,每个迭代实现不同的特性。重大的、优先级高的特性优先实现,风险高的特性优先实现。在项目的早期就将软件的原型开发出来,并基于这个原型在后续的迭代不断完善。迭代开发的好处是:尽早编码,尽早暴露项目的技术风险。尽早使客户见到可运行的软件,并提出优化意见。可以分阶段提早向不同的客户交付可用的版本。
在每个迭代中,架构师负责将所有的特性分解成多个Story Card。每个Story可以视为一个独立的特性。每个Story应该可以在最多1个星期内完成开发,交付提前测试(Pre-Test)。当一个迭代中的所有Story开发完毕以后,测试组再进行完整的测试。在整个测试过程中(pre-test,test),基于Daily build,测试组永远都是每天从配置库上取下最新编译的版本进行测试,开发人员也随时修改测试人员提交的问题单,并合入配置库。
敏捷开发的一个特点是开放式办公,充分沟通,包括测试人员也和开发人员一起办公。基于Story Card的开发方式,团队会在开放式办公区域放置一块白板,上面粘贴着所有的Story Card,按当前的开发状态贴在4个区域中,分别是:未开发,开发中,预测试中,测试中。Story Card的开发人员和测试人员根据开发进度在Story Wall上移动Story Card,更新Story Card的状态。这种方式可以对项目开发进度有一个非常直观的了解。
敏捷开发宣言如下:
-
个体和交互 胜过 过程和工具
-
可以工作的软件 胜过 面面俱到的文档
-
客户合作 胜过 合同谈判
-
响应变化 胜过 遵循计划
-
虽然右项也有价值,但是我们认为左项具有更大的价值。
敏捷开发的方式
敏捷开发作为一种指导思想或开发方式,Scrum和XP(Extreme Programming:极限编程)是敏捷开发的具体方式。Scrum和XP的区别是,Scrum偏重于过程,XP则偏重于实践,但是实际中,两者是结合一起应用的。
Scrum方式
Scrum的英文意思是橄榄球运动的一个专业术语,表示“争球”的动作;把一个开发流程的名字取名为Scrum,大家像打橄榄球一样迅速、富有战斗激情,运用该流程,你就能看到你团队高效的工作。
Scrum整个开发过程由若干个短的迭代周期组成,一个短的迭代周期称为一个Sprint,每个Sprint的建议长度是2到4周(互联网产品研发可以使用1周的Sprint)。在Scrum中,使用Product Backlog来管理产品的需求,Product backlog是一个按照商业价值排序的需求列表,Scrum团队总是先开发对客户具有较高价值的需求。在Sprint中,Scrum团队从产品Backlog中挑选最高优先级的需求进行开发。挑选的需求在Sprint计划会议上经过讨论、分析和估算得到相应的任务列表,我们称它为Sprint backlog。在每个迭代结束时,Scrum团队将递交潜在可交付的产品增量。Scrum 采用迭代、增量的方法来优化可预见性并控制风险。
Scrum开发流程中的三大角色
1.产品负责人(Product Owner)
主要负责确定产品的功能和达到要求的标准,指定软件的发布日期和交付的内容,同时有权力接受或拒绝开发团队的工作成果。
2.流程管理员(Scrum Master)
主要负责整个Scrum流程在项目中的顺利实施和进行,以及清除挡在客户和开发工作之间的沟通障碍,使得客户可以直接驱动开发。
3.开发团队(Scrum Team)
主要负责软件产品在Scrum规定流程下进行开发工作,人数控制在5~10人左右,每个成员可能负责不同的技术方面,但要求每成员必须要有很强的自我管理能力,同时具有一定的表达能力;成员可以采用任何工作方式,只要能达到Sprint的目标。
进行Scrum开发的流程
1.我们首先需要确定一个Product Backlog(按优先顺序排列的一个产品需求列表),这个是由Product Owner 负责的;
2.Scrum Team根据Product Backlog列表,做工作量的预估和安排;
3.有了Product Backlog列表,我们需要通过 Sprint Planning Meeting(Sprint计划会议) 来从中挑选出一个Story作为本次迭代完成的目标,这个目标的时间周期是1~4个星期,然后把这个Story进行细化,形成一个Sprint Backlog;
4.Sprint Backlog是由Scrum Team去完成的,每个成员根据Sprint Backlog再细化成更小的任务(细到每个任务的工作量在2天内能完成);
5.在Scrum Team完成计划会议上选出的Sprint Backlog过程中,需要进行 Daily Scrum Meeting(每日站立会议),每次会议控制在15分钟左右,每个人都必须发言,并且要向所有成员当面汇报你昨天完成了什么,并且向所有成员承诺你今天要完成什么,同时遇到不能解决的问题也可以提出,每个人回答完成后,要走到黑板前更新自己的 Sprint burn down(Sprint燃尽图);
6.做到每日集成,也就是每天都要有一个可以成功编译、并且可以演示的版本;很多人可能还没有用过自动化的每日集成,其实TFS就有这个功能,它可以支持每次有成员进行签入操作的时候,在服务器上自动获取最新版本,然后在服务器中编译,如果通过则马上再执行单元测试代码,如果也全部通过,则将该版本发布,这时一次正式的签入操作才保存到TFS中,中间有任何失败,都会用邮件通知项目管理人员;
7.当一个Story完成,也就是Sprint Backlog被完成,也就表示一次Sprint完成,这时,我们要进行 Srpint Review Meeting(演示会议),也称为评审会议,产品负责人和客户都要参加(最好本公司老板也参加),每一个Scrum Team的成员都要向他们演示自己完成的软件产品(这个会议非常重要,一定不能取消);
8.最后就是 Sprint Retrospective Meeting(回顾会议),也称为总结会议,以轮流发言方式进行,每个人都要发言,总结并讨论改进的地方,放入下一轮Sprint的产品需求中;
XP方式
极限编程是一个轻量级的、灵巧的软件开发方法;同时它也是一个非常严谨和周密的方法。它的基础和价值观是交流、朴素、反馈和勇气;即,任何一个软件项目都可以从四个方面入手进行改善:加强交流;从简单做起;寻求反馈;勇于实事求是。XP是一种近螺旋式的开发方法,它将复杂的开发过程分解为一个个相对比较简单的小周期;通过积极的交流、反馈以及其它一系列的方法,开发人员和客户可以非常清楚开发进度、变化、待解决的问题和潜在的困难等,并根据实际情况及时地调整开发过程。
XP的十三种核心实践:
-
团队协作(Whole Team)
-
规划策略(The Planning Game)
-
结对编程(Pair programming)
-
测试驱动开发(Testing-Driven Development)
-
重构(Refactoring)
-
简单设计(Simple Design)
-
代码集体所有权(Collective Code Ownership)
-
持续集成(Continuous Integration)
-
客户测试(Customer Tests)
-
小型发布(Small Release)
-
每周40小时工作制(40-hour Week)
-
编码规范(Code Standards)
-
系统隐喻(System Metaphor)
关于规划策略:计划是持续的、循序渐进的。每2周,开发人员就为下2周估算候选特性的成本,而客户则根据成本和商务价值来选择要实现的特性。
关于测试驱动开发:编写单元测试是一个验证行为,更是一个设计行为。同样,它更是一种编写文档的行为。编写单元测试避免了相当数量的反馈循环,尤其是功功能能验证方面的反馈循环。程序员以非常短的循环周期工作,他们先增加一个失败的测试,然后使之通过。
关于隐喻:隐喻同体系结构是同义词,隐喻用于描述项目的全貌,Story用于描述个别具体的特征。隐喻是将整个系统联系在一起的全局视图;它是系统的未来影像,是它使得所有单独模块的位置和外观变得明显直观。如果模块的外观与整个隐喻不符,那么你就知道该模块是错误的
XP的一个成功因素是重视客户的反馈——开发的目的就是为了满足客户的需要。XP方法使开发人员始终都能自信地面对客户需求的变化。XP强调团队合作,经理、客户和开发人员都是开发团队中的一员。团队通过相互之间的充分交流和合作,使用XP这种简单但有效的方式,努力开发出高质量的软件。XP的设计简单而高效;程序员们通过测试获得客户反馈,并根据变化修改代码和设计,他们总是争取尽可能早地将软件交付给客户。XP程序员能够勇于面对需求和技术上的变化。
优秀团队的选择
优秀的团队并不拘泥于某种开发方式的,而是选择最适合的方式。如果公司的理念恰恰是:开放、协作性强、扁平化团队,以用户为中心。那么使用"敏捷开发方法"恰好暗合,由此也会成为一种榜样来对其它团队进行激励。如果一个团队一百多人,强求于此也大可未必。
最好的,一定是最合适的。没有绝对的最好,只有相对的更好。