编程心法口诀


写给自己的编程过程中需要时刻考虑的几大要素,以后写程序前都要默念几遍,嘿嘿

编程心法口诀

由于主要使用的语言是C++,编程过程中要注意的东西,主要是以下这几点。

1.出错处理——对出错情况的处理以及报告出错原因

2.编码规范——要写别人看得懂,看起来舒服的代码

3.设计模式——编码时要注意考虑以后功能的扩展点,并把变化封装到一个地方

4.内存安全——内存泄漏,C++的天坑,必须考虑

5.线程安全——现在这编码环境,多线程满地走,不会写它不如狗,得靠线程安全稳一手

1 出错处理

在程序执行过程中,出现错误是难以避免的,在编码过程中我们需要考虑各种出错情况

1.1 传统出错处理的方式

出错处理方式常见的有四种。
1.通过函数的返回值标识是否有错,然后通过全局的errno变量配上一个errstr数组说明为什么出错
这种方式一般在c语言中使用。
使用这种方式容易忘记检查返回值,且函数返回值的接口不纯,正常值与返回值混合在一起,容易出错。

2.通过返回码来指针程序是否运行正常,input和output都通过函数参数来完成
这种方式一般在windows系统调用中使用
该方式使得函数参数语义复杂,既有出参数又有入参数,且依旧会出现忘记检查返回值的情况。

3.多返回值
令函数返回result和errno两个值
Go和Python支持这种方式进行出错处理
有多个不同类型的errno时,要写很多if err != nil语句。逻辑代码与出错处理代码相混杂。

4.异常捕捉
try - catch - finally语义进行异常处理
Java支持的很好
异常捕捉避免了上述处理方法的一些问题,但由于异常捕捉底层需要函数栈的支持,出现错误时对性能影响较大,且在多线程编程中局限性很大,一个线程无法捕获其他线程丢出的异常。

1.2 错误分类

从错误原因的维度看,出错主要有3种情况。写程序时应该往这方面考虑是否会有出错的情况,并做好出错情况的处理。

1.资源的错误,如申请内存错误,打开没有权限的文件按,文件的写错误,发送文件到网络端发现网络故障。属于运行环境的错误,有的无法处理(如内存溢出,栈耗尽),只能退出程序,有的是可以处理的。

2.程序错误,这属于自己写的程序问题,如空指针,非法参数等。需要记在日志中方便我们排查

3.用户错误。用户的非法请求和非法输入等,一般处理方法为向用户端报错,程序正常运行。
也可以将错误记下做统计,方便以后程序的改进。

从是否能够处理的角度看,分为两种。
第一为我们能够处理的错误
第二种为我们不能够处理的错误。

1.3 错误处理方法使用时机

通常的原则。
程序中的错误,可能用异常捕捉会比较合适;
用户的错误,用返回码比较合适;
而资源类的错误,要分情况,是用异常捕捉还是用返回值。如果资源类错误属于可以解决的情况,可以用返回码,如果是无法处理的情况一般丢异常。

但是有一些限制
异常捕捉只能在同步情况下使用
分布式情况调用远程服务只能看错误返回码

1.4 多线程编程错误处理

待补充

2 编码规范

2.1 代码的坏味道

1.保证对象封装,利用行为更改对象属性而不是使用setter

//通过某本书的审核状态
//bad code
public void approve(final long bookid){
	book.setReviewStatus(ReviewStatus.APPROVED)
}
//修改为
public void approve(final long bookid){
	book.approve();
}

2.命名应该带有业务含义

//一个将状态改为翻译中的函数
//函数命名为public void processchapter(long bookid);
//函数名更改为startTranslation会更加合适 

3.命名应该避免暴露太多技术细节,保持抽象

避免bookList这样的命名,因为如果修改book的存储容器,很可能之前的代码都要修改名字

4.命名应该符合英语语法,避免单词拼写错误

函数表示动作,所以函数名应该是一个动词,或者动宾短语

5.英语中有很多同义词,应该选择最适合场景的那一个,避免中国式英语
6.避免单词拼写错误,给人带来混淆
7.避免写重复代码

代码重复分为几种
复制粘贴的代码:很好发现,避免
功能重复,比如一个修改密码的函数被用不同的函数名修改了两次,这种情况一般在项目交接种会产生。避免这种情况需要对现在接收的项目及已有工作有深刻认识
执行重复:代码有些同样的功能执行了几次
这种情况比较难发现
比如有个校验用户信息的函数
函数1 校验用户名
{
	访问内存
	校验
}
函数2 校验用户密码
{
	访问内存
	校验
}
这种情况,访问内存比较耗时,而实际上我们访问内存一次就可以实现我们的需求,这算是功能重复的一种

8.避免写长函数
保证代码在30-50行内解决问题,如果不能解决,我们也许需要新定义一个函数

9.避免判断逻辑过于复杂
比如错误处理的代码
要在if中判断多种错误情况,这时我们可以通过给错误分类的情况去处理,这样可以使得判断逻辑变得简单易读。

10.别写大类
别人是很难看懂一眼望不到边的类的
如果出现这种情况,我们可以对类的职责进一步划分

11.避免一个函数的参数太长
可以将参数分类,变成对象去封装。
可以将参数进行动静分离,不会改变的参数变为函数的内部变量或类的成员字段。

12.避免for和if嵌套太深
看情况解决

13.尽量在变量声明时完成初始化
如果之后忘了的话,找bug的过程会很让人崩溃

3 设计模式

要让代码应对变化

3.1 设计原则

SOLID原则
单一职责,开闭,里氏替换,接口隔离,依赖倒置
类功能保持单一,善用抽象,接口保证最小

DRY原则:不要重复

KISS和YANGI原则:保持简单,别为太远的事考虑,但要为最近的事留下扩展点

3.2 不同场景下该用的设计模式

设计模式并不是万能的,实际情况还得具体分析,甚至经常有混用设计模式的情形。但我们最终目的还是使得类清晰易懂,出现变化时修改尽可能少的代码。

创建对象
保证类只有一个对象:单例模式
根据参数创建相似的对象:工厂
得到当前对象的完整拷贝:原型
一个对象的构建过程很复杂:建造者

非创建对象 关于类的结构
不希望直接使用对象(对象为远程对象,使用对象需要安全控制,直接创建对象开销大,),在对对象的访问时加一层代理,给类增加非功能性需求:代理
给原始类增加功能:装饰器 典型为java的inputStream和outputStream
一个操作由多种抽象的因素影响:桥接模式
想利用一个接口,但这个接口不完全符合我们的使用要求:适配器
要实现一个功能调用的接口太多,可以将n个接口封装成一个:门面模式
类呈树形结构,如学校,学院,系。文件系统:组合模式
对象创建出来之后不再变化,且对象种类较少:享元模式(对象池,copy on write)

关于类的行为
一个对象状态改变,其他对象要知道:观察者模式
代码中只有几个变化点,其余大部分不会变化:模板模式
业务场景多变,不同场景用到不同的算法:策略模式
一个请求需要被多方处理,或者一个请求需要按情况来决定被谁处理:职责链
对象具有多种状态,需要对外输出不同行为的问题,状态间可以相互转换:状态模式
容器中遍历不同对象:迭代器模式
一个对象要和多个对象交互的场景:中介模式 !慎用!
解释语言类场景:解释器模式
某些需要防止丢失,撤销操作,恢复状态等功能的对象:备忘录模式
需要控制某些命令的执行情况或给命令附加功能:命令模式

还有一个访问者模式,这个比较难,场景也较少。
它允许多个操作运用到一组对象上,将操作和对象本身进行解耦。以后想到了什么适用场景再补充

4 内存安全

4.1 C++内存管理方式

C++内存管理有两种。
第一种为利用栈管理内存,这种在对象的作用范围内可以自维护
第二种为利用堆,但是申请到的内存需要我们自己delete,这种方式比较危险,很可能会发生没有没有delete的情况。有时即使写了delete,也会由于丢出了异常,导致delete实际没有执行

4.2 如何做到内存安全

第一原则:尽可能的使用对象本身,避免使用动态内存分配。

1.要使用引用语义的情况,需要用到指针。使用引用来代替
2.使用多态的情况,用传引用代替传指针来使用多态

若必须用到堆内存
情况如下:
1.对象太大,堆栈无法装下

2.需要按情况申请内存(如动态数组)

3.需要利用类的返回值实现多态(工厂方法),此时无法用引用代替

自己将得到的指针用对象封装起来,利用栈的内存特性即对象离开作用域后会调用析构函数,在析构函数中进行资源释放
更好的办法就是智能指针。智能指针实现原理其实就是前一种方法
主要使用的有两种:unique_ptr,share_ptr

5 线程安全

线程安全是多线程编程时必须关注的问题

5.1 线程安全错误之源

线程安全的根源在于3点。
缓存的可见性:每个CPU核心有其自己的一级缓存

指令的乱序性:来源于编译器的优化,指令的并行执行(在一个线程内,指令顺序相对独立,乱序不影响结果,但其他线程的执行会依赖该线程的指令编写顺序)

原子性:线程安全主要发生在多个线程对共享数据的修改上,但问题就出现在修改数据不是原子性的,这时会发生未知错误。

5.2 常见场景如何做到线程安全且并发度高

暂时只了解java的解决方案。
前两种问题是java通过内存模型中的happens before规则来实现。
后一种问题是通过加锁解决

主要分析通过加锁的方式做到线程安全

场景二

5.2.1 要根据一个可能被修改的共享数据决定是否修改对象
public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null)
    	instance = new Singleton();
    }
    return instance;
  }
}

要根据instance状态决定是否调用初始函数,不考虑线程安全会出问题

public class SafeWM {
  // 库存上限
  private final AtomicLong upper =
        new AtomicLong(0);
  // 库存下限
  private final AtomicLong lower =
        new AtomicLong(0);
  // 设置库存上限
  void setUpper(long v){
    // 检查参数合法性
    if (v < lower.get()) {
      throw new IllegalArgumentException();
    }
    upper.set(v);
  }
  // 设置库存下限
  void setLower(long v){
    // 检查参数合法性
    if (v > upper.get()) {
      throw new IllegalArgumentException();
    }
    lower.set(v);
  }
  // 省略其他业务代码
}

lower是否设置成功取决于upper,upper是否修改成功取决于lower,不考虑线程安全会出问题
解决方案在我另一篇博客中->并发相关的一些技术

场景二

5.2.2 一个操作涉及几个对象,且对象数据都要修改

最典型的为转账操作

class Account {
  private int balance;
  // 转账
  synchronized void transfer(
      Account target, int amt){
    if (this.balance > amt) {
      this.balance -= amt;
      target.balance += amt;
    }
  } 
}

解决办法为给两个对象加锁

class Account {
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    // 锁定转出账户
    synchronized(this) {              
      // 锁定转入账户
      synchronized(target) {           
        if (this.balance > amt) {
          this.balance -= amt;
          target.balance += amt;
        }
      }
    }
  } 
}

5.3 死锁及解决策略

场景2可能会发生死锁。
如果账户A和账户B同时向对方执行转账操作,
账户A锁住了账户B的对象,账户B锁住了A的对象,两者都无法继续进行,导致死锁。

可以从死锁产生的条件来避免死锁

死锁有4个条件。
互斥占有并等待不可抢占循环等待
可以从后面三个入手

1.占有并等待
我们可以拿到所有资源之后再继续进行程序处理。
在这个问题的具体操作为在锁this和target前再加一把锁lock,锁住了this和target后就解锁lock。

2.不可抢占条件
我们可以在无法申请到资源之后立刻释放我们已拥有的资源,由于synchronized申请失败之后立刻陷入阻塞,无法实现。但可以在java中可以利用java.util.concurrent提供的lock中实现。该方法虽然不会导致死锁,但可能会导致活锁。解决办法为释放资源之后随机等待一个时间再去申请资源。

3.循环等待条件
将资源进行排序,所有对象都按资源序号小的先拿,这样就不会产生环形等待条件了。
在这个问题中可以给count提供一个id进行排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值