API风云录

API设计挑战
本文通过一个项目的实际案例,探讨了API设计过程中的各种挑战与反思,包括接口管理、扩展性、性能优化等方面。

好吧,我承认我是标题党,还是让我们从一个故事开始吧。

项目的业务逻辑层需要被设计成一个具备易扩展的模式,对外提供了大小相异的API。项目组人人头脑风暴,最后在各位的努力下,克服苦难,业务逻辑层被封装起来,一组最初的API被提供出来:

1、现有Service逻辑已经疏于管理,欠缺重构,变成了不易控制的逻辑层,接口众多,鱼龙混杂,难以规整出清晰、可用的接口给第三方(例如下游定制团队),怎么办?
Web应用有个特点,当你对代码的管理缺乏控制而搞不定时,可以在其上封装一层,这是一个通用的解决办法,也是一柄双刃剑。
正如某同事“我们都是工程商人”所述,心底里可能我们愿意追求代码的曼妙和清晰,但是大多数软件项目都应建立在“赚钱”的前提之上。
于是大家各抒己见,最终将原有Service之上的Facade改头换面,重新整顿了一把,变成了XXXManagementUtil。

2、有人考虑扩展方便,将这些API门面类中的方法参数,全部使用Event封装起来,这样的好处在于添加参数的情况下不必修改任何接口方法:

 

 

class UserManagementUtil{ 
    public UserInfo getUser(GetUserEvt); 
}
 

3、Event里面的参数哪些可选、哪些必选?应当满足怎样的取值规则?于是在Event的接口中引入了verify方法,子类中重写了toString方法,并利用Spring的AOP机制,在API调用时进行参数校验并打印日志。

4、API的接口应该根据什么来划分呢?
按照模型驱动来划分吧,有声音说,比如UserManagementUtil、SongManagementUtil。
可是更多的声音说:业务中有Song、Music等等二十多种内容类型,不觉得太庞大了吗?还是把Song、Music等发布的内容涉及到的API都归结到ContentManagementUtil和ContentExtManagementUtil里面吧,不要细分了。

5、API的接口粒度应该怎样控制呢?
有同事表示,提供简易的接口,就如同Windows提供的API一样,那么,我们提供基于模型的CRUD方法吧,这样方法既原子、纯净,通过外部调用者一定的组合,又能满足外部调用的功能。

6、怎样让API便于外部调用呢?
一开始要求外部使用Spring注入的方式来使用API的建议遭到了一些反对,我们不是要让调用方用得灵活方便、降低定制难度对吗?
又有一个声音说:把方法都变成static吧?于是又遭来一些反对的声音,static方法可能带来API中资源依赖和资源初始化的问题。
最后API外部的调用变成了下面这样,而API内依然由Spring来管理:

UserManagementUtil.getInstance().getUser(evt);
 

7、怎样让API便于定制人员理解呢?
需要强化API的JavaDoc,其中需要包含足够的方法功能和流程、参数以及返回值的说明。

……

于是大家大干一场,API渐渐新鲜出炉了,一切看起来是那么美好。

 

--------------------------------------------------------------------------------

 

不过数日之后,许多人渐渐开始发现,看起来那么美好的事情实际上好像也并不那么美好:

1、考虑到外部接口调用功能和性能的问题,通用和简单的接口已经完全无法满足业务需要,多次API调用可能意味着多次与数据资源交互,如果能把多次交互合并成一次(例如底层使用一次数据库连表查询实现),性能就可以大大提升。于是一些功能庞大的接口开始出现,并且愈来愈不受控制了。

2、方法参数Event的境遇如何呢?也不好。调用者并不十分清楚Event中哪些参数是必选的,那么,就把所有参数都传进去吧!于是API以外的Action层面到处是这样丑陋的代码:

 

 

GetUserEvt event = new GetUserEvt(); 
event.setAccountName("13000000000"); 
event.setAddress("xxx"); 
event.setType(xx); 
event.setAge(xx); 
……(此处省略N行)
 

3、verify方法呢?toString方法呢?
随着项目的进行,这些都变得不可控了,写一个简单根据ID来获取模型POJO的方法,就要写一个Evt,还有一堆冗长的verify和toString方法,开发人员变得不那么情愿,这两个方法就写得越来越简陋了。
更糟的是,这里用到的Spring的AOP方式还在项目中被发现为性能瓶颈,于是有更多的人开始怀疑最初这个决定的正确性了。

4、ContentManagementUtil和ContentExtManagementUtil变得非常庞大,困惑越来越多,一些方法也不知该往哪放了,比如getContentsByCategory方法,到底是放到ContentManagementUtil里呢,还是放到CategoryManagementUtil里呢?

5、JavaDoc变成了真正定制的瓶颈,定制人员不断表示无法读懂JavaDoc,不知道该怎样调用API;而开发人员呢,则不断抱怨JavaDoc工作量巨大,要把一个接口的JavaDoc写清楚,需要描述接口内部流程、参数名称、参数含义、使用场景、不同场景下需要哪些参数、返回对象含义、异常类型、异常返回码可能的取值、调用示例……一句话,变得无比困难。

6、需求变化频繁,当接口版本更新时,接口调用者发现,糟糕,原来的方法调用变得不可用了,但是是哪个参数不正确或缺失造成的呢?也没法看出来。

……

 

--------------------------------------------------------------------------------

 

这就是一个简简单单的API风云录,一段API诞生和撞到新秀墙的困惑史,一切看起来都很自然,也许你感到些许熟悉,我就说到这里,如果有一些感触,这些真实的记录就变得有价值。

我说完了。

……

 

可是我怎么甘心就这么“说完了”?

这不是我的风格。

我要痛批一顿?要引出所谓“真正正确”的做法?

当然不是!这样的事情还是留给专家学者去做吧。

 

--------------------------------------------------------------------------------

 

API的设计是软件架构设计的细化和缩影,是一件持续的工作,一样没有银弹,一样没有一劳永逸的可能。它历来是一个难题,无论在最初看似多么“完美”的规划和安排,最后都可能变成鸡肋;而且,看起来越强大、兼容性越好的设计,就越有可能打了水漂

1、既然给不出一个完整和绝对正确的办法,API从诞生之日起,就需要开发人员不断对其修整和维护,使之不断适应当前应用需要,从而避免其老化。软件的架构需要维护,一个再出色的架构师完成了他的设计,如果开发团队不能贯彻并把基本的架构思想传递下去,项目一样还会偏离预想的轨道(不一定是好或者坏的结果,但通常都是不合预期的),这一点,API也一样,设计人员应当参与API一版版的发展和发布。
对于一些业务性很强的API,需要API编写的门槛会提高,需要开发人员理解API的原则,清楚细化了的要求。

2、设计一个易用、简单和清晰的API基本规则,在API发展的过程中,大小规模的重构不可避免且理所应当,基本规则就像软件架构一样,不会轻易变更,最初设定的规则越复杂,后续变更和成熟的过程越痛苦。重构本身就是版本发展的一部分,更多的特性应当在后续的重构中丰满,而不是在最初预留好一个准许“上帝功能”都能便捷扩展的能力。

3、保持基础接口的兼容性。JDK的HashTable有一个containsValue方法,还有一个contains方法,二者功能上完全一样,之所以搞这样两个完全一样的方法,正是由于历史原因造成的。JDK1.2才正式引入Java Collections Framework,抽象了Map接口,才有了containsValue方法,而之前的方法因为向下兼容的原因无法删除。我不能评估说这是一个好的兼容还是一个糟糕的妥协,但至少,JDK都为了保持了基础接口的兼容性而做了这样一件看似不合理的事。
有一种不激进的思路是,给一些将要废弃的接口置为@deprecated,待若干个版本后可以选择删除。

4、给API设计人员以充分的信任。API的设计不是民主选举,少数服从多数,把不可抗拒的要求和额外的需求陈述清楚,就不应过于干涉其组织的讨论。通常软件的设计都有这样一个惯性问题,不是最终采纳合理的方案、成熟的方案,而是采纳具备话语权的人的意见,或是经过民主式的妥协来完成设计

5、严格控制接口数量。性能、可维护性,二者谁更重要?事实上这两者在很多情况下都是一组矛盾,平衡二者的关系才是设计者应当考虑的。如果把数个行为放置到一个接口中,当然可以提升性能,但是也增加了接口,增大了维护成本。尤其对于成熟的API来说,每增加一个接口都应是慎重的行为,如果项目组自我管理能力不够,就需要专人集中守护

6、发布稳定和成熟的API。业界有一句玩笑话叫“不要使用3.0版本以下的软件”,正是说的这个道理,经过少数几轮迭代的API还远不稳定,而且可能还有众多bug,后续大规模的变更就会令API失去价值。如果由于不可抗因素,API变更实在太大,考虑提纯API的功能,尽量简单的方法,将复杂的关系条件交给调用方,会减小需求变更带来的冲击

举例来说,UserInfo模型最初设计的相关接口有:

 

 

queryUserInfoByName(String name) 
queryUserInfoByAccountNumber(String accountNumber) 

 
但倘若模型变更频繁,那么可以考虑设计这样的接口:

 

queryUserInfo(Map queryCriteria) 

 
其中的参数QueryCriteria代表着了查询条件,比如这样调用:

 

 

queryUserInfo({name:"%abc%",accountNumber:"139%"}) 

 
(降低了可读性和调用的便捷程度,但提高了接口稳定性)

7、接口尽量独立,避免发布互相之间有依赖关系的接口。如果实在避免不了,最好让两个有依赖关系的接口放置在相近的地方,以便查看。

8、接口必须被完全理解,最好简洁易懂。如果接口复杂,那你可能寄望于详尽的JavaDoc来说明,如果接口简单,完全可以只需要很少的说明,成为自注释的。

 

最后我用一张Google API的截图结束本文:



 

 

文章系本人原创,转载请注明作者和出处

本课题设计了一种利用Matlab平台开发的植物叶片健康状态识别方案,重点融合了色彩与纹理双重特征以实现对叶片病害的自动化判别。该系统构建了直观的图形操作界面,便于用户提交叶片影像并快速获得分析结论。Matlab作为具备高效数值计算与数据处理能力的工具,在图像分析与模式分类领域应用广泛,本项目正是借助其功能解决农业病害监测的实际问题。 在色彩特征分析方面,叶片影像的颜色分布常与其生理状态密切相关。通常,健康的叶片呈现绿色,而出现黄化、褐变等异常色彩往往指示病害或虫害的发生。Matlab提供了一系列图像处理函数,例如可通过色彩空间转换与直方图统计来量化颜色属性。通过计算各颜色通道的统计参数(如均值、标准差及主成分等),能够提取具有判别力的色彩特征,从而为不同病害类别的区分提供依据。 纹理特征则用于描述叶片表面的微观结构与形态变化,如病斑、皱缩或裂纹等。Matlab中的灰度共生矩阵计算函数可用于提取对比度、均匀性、相关性等纹理指标。此外,局部二值模式与Gabor滤波等方法也能从多尺度刻画纹理细节,进一步增强病害识别的鲁棒性。 系统的人机交互界面基于Matlab的图形用户界面开发环境实现。用户可通过该界面上传待检图像,系统将自动执行图像预处理、特征抽取与分类判断。采用的分类模型包括支持向量机、决策树等机器学习方法,通过对已标注样本的训练,模型能够依据新图像的特征向量预测其所属的病害类别。 此类课题设计有助于深化对Matlab编程、图像处理技术与模式识别原理的理解。通过完整实现从特征提取到分类决策的流程,学生能够将理论知识与实际应用相结合,提升解决复杂工程问题的能力。总体而言,该叶片病害检测系统涵盖了图像分析、特征融合、分类算法及界面开发等多个技术环节,为学习与掌握基于Matlab的智能检测技术提供了综合性实践案例。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值