欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-14 18:14:10
...

前言:工作中发现一些服务提供的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