您的位置:首页 > 其它

风格纠错题

2020-01-15 11:51 781 查看

出题者简介: 孙源(sunnyxx),目前就职于百度

整理者简介:陈奕龙,目前就职于滴滴出行。

转载者:豆电雨(starain)微信:doudianyu

 

 

 


修改完的代码:

修改方法有很多种,现给出一种做示例:

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 修改完的代码,这是第一种修改方法,后面会给出第二种修改方法

typedef NS_ENUM(NSInteger, CYLSex) {
CYLSexMan,
CYLSexWoman
};

@interface CYLUser : NSObject<NSCopying>

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end

下面对具体修改的地方,分两部分做下介绍:硬伤部分优化部分 。因为硬伤部分没什么技术含量,为了节省大家时间,放在后面讲,大神请直接看优化部分

优化部分

  1. enum 建议使用 

    NS_ENUM
     和 
    NS_OPTIONS
     宏来定义枚举类型,参见官方的 Adopting Modern Objective-C 一文:

    //定义一个枚举
    typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
    };

    (仅仅让性别包含男和女可能并不严谨,最严谨的做法可以参考 这里 。)

  2. age 属性的类型:应避免使用基本类型,建议使用 Foundation 数据类型,对应关系如下:

    int -> NSInteger
    unsigned -> NSUInteger
    float -> CGFloat
    动画时间 -> NSTimeInterval

    同时考虑到 age 的特点,应使用 NSUInteger ,而非 int 。 这样做的是基于64-bit 适配考虑,详情可参考出题者的博文《64-bit Tips》

  3. 如果工程项目非常庞大,需要拆分成不同的模块,可以在类、typedef宏命名的时候使用前缀。

  4. doLogIn方法不应写在该类中:

    虽然

    LogIn
    的命名不太清晰,但笔者猜测是login的意思, (勘误:Login是名词,LogIn 是动词,都表示登陆的意思。见: Log in vs. login 

    登录操作属于业务逻辑,观察类名 UserModel ,以及属性的命名方式,该类应该是一个 Model 而不是一个“ MVVM 模式下的 ViewModel ”:

     

    无论是 MVC 模式还是 MVVM 模式,业务逻辑都不应当写在 Model 里:MVC 应在 C,MVVM 应在 VM。

    (如果抛开命名规范,假设该类真的是 MVVM 模式里的 ViewModel ,那么 UserModel 这个类可能对应的是用户注册页面,如果有特殊的业务需求,比如: 

    -logIn
     对应的应当是注册并登录的一个 Button ,出现 
    -logIn
     方法也可能是合理的。)

  5. doLogIn 方法命名不规范:添加了多余的动词前缀。 请牢记:

    如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用 

    do
    does
     这种多余的关键字,动词本身的暗示就足够了。

    应为 

    -logIn
     (注意: 
    Login
     是名词, 
    LogIn
     是动词,都表示登陆。 见 Log in vs. login 

  6. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
    方法中不要用 
    with
     来连接两个参数: 
    withAge:
     应当换为
    age:
    age:
     已经足以清晰说明参数的作用,也不建议用 
    andAge:
     :通常情况下,即使有类似 
    withA:withB:
     的命名需求,也通常是使用
    withA:andB:
     这种命名,用来表示方法执行了两个相对独立的操作(从设计上来说,这时候也可以拆分成两个独立的方法),它不应该用作阐明有多个参数,比如下面的:

    //错误,不要使用"and"来连接参数
    - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
    //错误,不要使用"and"来阐明有多个参数
    - (instancetype)initWithName:(CGFloat)width andAge:(CGFloat)height;
    //正确,使用"and"来表示两个相对独立的操作
    - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
  7. 由于字符串值可能会改变,所以要把相关属性的“内存管理语义”声明为 copy 。(原因在下文有详细论述:用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?)

  8. “性别”(sex)属性的:该类中只给出了一种“初始化方法” (initializer)用于设置“姓名”(Name)和“年龄”(Age)的初始值,那如何对“性别”(Sex)初始化?

    Objective-C 有 designated 和 secondary 初始化方法的观念。 designated 初始化方法是提供所有的参数,secondary 初始化方法是一个或多个,并且提供一个或者更多的默认参数来调用 designated 初始化方法的初始化方法。举例说明:

    // .m文件
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    //
    
    @implementation CYLUser
    
    - (instancetype)initWithName:(NSString *)name
    age:(NSUInteger)age
    sex:(CYLSex)sex {
    if(self = [super init]) {
    _name = [name copy];
    _age = age;
    _sex = sex;
    }
    return self;
    }
    
    - (instancetype)initWithName:(NSString *)name
    age:(NSUInteger)age {
    return [self initWithName:name age:age sex:nil];
    }
    
    @end

    上面的代码中initWithName:age:sex: 就是 designated 初始化方法,另外的是 secondary 初始化方法。因为仅仅是调用类实现的 designated 初始化方法。

    因为出题者没有给出 

    .m
     文件,所以有两种猜测:1:本来打算只设计一个 designated 初始化方法,但漏掉了“性别”(sex)属性。那么最终的修改代码就是上文给出的第一种修改方法。2:不打算初始时初始化“性别”(sex)属性,打算后期再修改,如果是这种情况,那么应该把“性别”(sex)属性设为 readwrite 属性,最终给出的修改代码应该是:

    // .h文件
    // http://weibo.com/luohanchenyilong/
    // https://github.com/ChenYilong
    // 第二种修改方法(基于第一种修改方法的基础上)
    
    typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
    };
    
    @interface CYLUser : NSObject<NSCopying>
    
    @property (nonatomic, readonly, copy) NSString *name;
    @property (nonatomic, readonly, assign) NSUInteger age;
    @property (nonatomic, readwrite, assign) CYLSex sex;
    
    - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
    - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
    + (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
    
    @end

    .h
     中暴露 designated 初始化方法,是为了方便子类化 (想了解更多,请戳--》 《禅与 Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译)》。)

  9. 按照接口设计的惯例,如果设计了“初始化方法” (initializer),也应当搭配一个快捷构造方法。而快捷构造方法的返回值,建议为 instancetype,为保持一致性,init 方法和快捷构造方法的返回类型最好都用 instancetype。

  10. 如果基于第一种修改方法:既然该类中已经有一个“初始化方法” (initializer),用于设置“姓名”(Name)、“年龄”(Age)和“性别”(Sex)的初始值: 那么在设计对应 

    @property
     时就应该尽量使用不可变的对象:其三个属性都应该设为“只读”。用初始化方法设置好属性值之后,就不能再改变了。在本例中,仍需声明属性的“内存管理语义”。于是可以把属性的定义改成这样

    @property (nonatomic, readonly, copy) NSString *name;
    @property (nonatomic, readonly, assign) NSUInteger age;
    @property (nonatomic, readonly, assign) CYLSex sex;
    由于是只读属性,所以编译器不会为其创建对应的“设置方法”,即便如此,我们还是要写上这些属性的语义,以此表明初始化方法在设置这些属性值时所用的方式。要是不写明语义的话,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。这种操作多余而且低效。
  11. initUserModelWithUserName
     如果改为 
    initWithName
     会更加简洁,而且足够清晰。
  12. UserModel
     如果改为 
    User
     会更加简洁,而且足够清晰。
  13. UserSex
    如果改为
    Sex
     会更加简洁,而且足够清晰。
  14. 第二个 

    @property
     中 assign 和 nonatomic 调换位置。 推荐按照下面的格式来定义属性

    @property (nonatomic, readwrite, copy) NSString *name;

    属性的参数应该按照下面的顺序排列: 原子性,读写 和 内存管理。 这样做你的属性更容易修改正确,并且更好阅读。这在《禅与Objective-C编程艺术 >》里有介绍。而且习惯上修改某个属性的修饰符时,一般从属性名从右向左搜索需要修动的修饰符。最可能从最右边开始修改这些属性的修饰符,根据经验这些修饰符被修改的可能性从高到底应为:内存管理 > 读写权限 >原子操作。

硬伤部分

  1. 在-和(void)之间应该有一个空格
  2. enum 中驼峰命名法和下划线命名法混用错误:枚举类型的命名规则和函数的命名规则相同:命名时使用驼峰命名法,勿使用下划线命名法。
  3. enum 左括号前加一个空格,或者将左括号换到下一行
  4. enum 右括号后加一个空格
  5. UserModel :NSObject
     应为
    UserModel : NSObject
    ,也就是
    :
    右侧少了一个空格。
  6. @interface
     与 
    @property
     属性声明中间应当间隔一行。
  7. 两个方法定义之间不需要换行,有时为了区分方法的功能也可间隔一行,但示例代码中间隔了两行。
  8. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
    方法中方法名与参数之间多了空格。而且 
    -
     与
    (id)
     之间少了空格。
  9. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
    方法中方法名与参数之间多了空格:
    (NSString*)name
     前多了空格。
  10. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;
     方法中 
    (NSString*)name
    ,应为 
    (NSString *)name
    ,少了空格。
  11. doLogIn方法中的 

    LogIn
     命名不清晰:笔者猜测是login的意思,应该是粗心手误造成的。 (勘误: 
    Login
     是名词,
    LogIn
     是动词,都表示登陆的意思。见: Log in vs. login 

转载于:https://www.cnblogs.com/starainDou/p/5253083.html

  • 点赞
  • 收藏
  • 分享
  • 文章举报
ad4576690 发布了1 篇原创文章 · 获赞 1 · 访问量 1448 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: