奇葩的sdk?sdk 设计该思考的一些原则

前言:工作中发现一些服务提供的sdk 真是非常难用,非常让人困惑,不禁想聊下自己设计sdk 会遵循哪些原则

  • 高内聚低耦合

往大点看,我们的库应该功能内聚,不要有业务侵入,往小点看我们的sdk,需要单一职责。先举个真实的反例:

	custom.Trace("xxx", "xxx")
	custom.Debug("xxx", "xxx")
	custom.Info("xxx", "xxx")

我司使用最多的日志库,竟然把特定业务 format 格式做了个default 句柄,然后放日志库里,做全局方法使用,README 也把这种类型放进去。然后只要是新使用的同学,看README后,都会跑来问我们,这个custom 什么意思,什么情况使用。这种设计是明显不合理的,给自己找麻烦,给别人输出疑惑。

  • 兼容性

每次改动发布是否符合最小语义化版本管理:https://semver.org/lang/zh-CN/。

有很长一段时间,我们公司没有版本管理,每次发布一次lib,就可能导致线上不知道哪里会出现问题。我们部门维护的sdk又很多,有不少在公司都几百个库在同时使用,我怎样确认我的这次修改发布不会给线上带来问题?后来我们引入了版本管理:

版本格式:主版本号.次版本号.修订号,版本号递增规则如下:

  1. 主版本号:当你做了不兼容的 API 修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。

举些例子,我添加了个private 的方法,我该怎样发版?我添加了个public 属性的方法我该怎样发版?我将一个public 的方法加了一个参数又该怎样发版,如果这个参数是defualt 呢?诸如此类,我们线上的每一次发布,都需要思考兼容性。

  • 可测试性

工程代码中,单测,benchmark 是比较基础的要求,测试覆盖度不到70%的sdk 安全性是很难保证的。通过提高测试覆盖率,我们确实自己解决了不少低级的bug。

可测试性需要包括data race 的测试。特别是含有并发的语言,比如java,golang,c,c++ 等,任何data race 都是bug,每次上线都应该包含data race 检测。举个例子感受下诡异行为:

package main

import (
    "fmt"
    "runtime"
    "time"
)

var i = 0

func main() {
    runtime.GOMAXPROCS(2)

    go func() {
        for {
            fmt.Println("i is", i)
            time.Sleep(time.Second)
        }
    }()

    for {
        i += 1
    }
}

可测试性还包括有效的错误处理和关键位置的debug日志。比如向下面的三种常见错误处理方式:

try {
	//do someing
}
catch (SomeException e){
	logger.error()
}
try {
	//do someing
}
catch (SomeException e){
	// ignore
}
 try {
	//do someing
}
catch (SomeException e){
	// xxx
	throw e
}

第二种处理方式,空白的错误处理,不打任何日志,不抛异常,简直就是线上的噩梦。

  • 安全性

安全性主要考虑的几个点是线程安全、原子性、防重入、阻塞还是非阻塞。

举个例子,redis https://github.com/gomodule/redigo 的Get 方法,不同的对象行为不一样,所有的Get 方法是线程安全的吗?Pool 的Get 方法是线程安全的吗?再或者不同的redis sdk 都一定有线程安全的Get 方法吗?

再看个例子,下面的syncAndReturnAll 方法是原子的吗?如果我需要原子取该怎样写?

//换成真实的redis实例
Jedis jedis = new Jedis();
//获取管道
Pipeline p = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
    p.get(i + "");
}
//获取结果
List<Object> results = p.syncAndReturnAll();

关于防重入,初始化方法、注册方法最容易资源泄露,比如下面两种写法,假如newClient 不是防重入的,有什么区别,会造成什么后果:

func NewBonusClient(disfName string) (*Client, error) {
	once.Do(func() {
		BonusClient, err = newClient(disfName)
	})
	return BonusClient, err
}

func NewBonusClient(disfName string) (*Client, error) {
	BonusClient, err = newClient(disfName)
	return BonusClient, err
}

  • 风格一致性

风格一致主要包含几个方面,第一个方面是,尽量遵循语言本身的规范,比如php 的PSR,python 的pep8 等;第二个方面是项目本身尽量保持风格一致,比如大小写,命名风格,代码组织风格等等。

  • 好的文档

究竟一个什么样的文档是好的文档?我个人认为,发布的任何sdk,不同基础的人都照着文档能看明白,不会有疑问来找自己,这种文档就是比较好的文档。

总结:写可读性强、健壮的代码,是对自己负责,也是对同事负责。

参考:

1,https://azure.github.io/azure-sdk/general_introduction.html#diagnosable

2,https://semver.org/lang/zh-CN/

3,https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/

4,https://docs.microsoft.com/en-us/azure/architecture/best-practices/api-design

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值