阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

从开源项目中总结出的几条编码经验

  • 2020-05-17
  • 本文字数:4274 字

    阅读完需:约 14 分钟

从开源项目中总结出的几条编码经验

一、背景

之前从事过几年 chromium(chrome 浏览器内核)和 android framework 的维护开发工作,这两个项目在开源界无论从应用范围、设计模式、技术深度等都是出类拔萃的项目。通过阅读这些优秀的源码,摘录出一些优秀的代码片段和编码技巧。最近两年把这些“片段”放到应用层的开发工作上,不仅在代码细节上有些许的性能提高,也能让项目的代码风格向这些顶尖项目靠近。同时,熟悉这些编码风格后,当我们在翻阅这些开源项目源码时,也能在一定程度上减少阅读障碍。


下面分享几个摘录出来的代码片段,再结合着这些代码在优酷项目上的使用,进行一一说明。希望对大家的开发工作起到一些借鉴意义。

二、使用注解,保证方法入参的合法性

当模块对外暴露一些 API 时,特别是输出 SDK 给外界使用时,为了保证调用方对方法入参的合法性,使用注解的方式来完成是个很好的解决方式,也可以减少不同模块开发人员间的沟通成本。


  1. 先来看看 chromium 使用注解的实际案例


public final class ViewportFit {   private static final boolean IS_EXTENSIBLE = false;
public static final int AUTO = 0; public static final int CONTAIN = 1; // AUTO + 1 public static final int COVER = 2; // CONTAIN + 1 public static final int COVER_FORCED_BY_USER_AGENT = 3; // COVER + 1
public static boolean isKnownValue(int value) { return value >= 0 && value <= 3; }
public static void validate(int value) { if (IS_EXTENSIBLE || isKnownValue(value)) return; throw new org.chromium.mojo.bindings.DeserializationException("Invalid enumvalue."); }
private ViewportFit() {}}
(https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java) /** * The Viewport Fit Type passed to viewportFitChanged. This is mirrored * in an enum in display_cutout.mojom. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ViewportFit.AUTO, ViewportFit.CONTAIN, ViewportFit.COVER}) public @interface ViewportFitType {}
复制代码


之后在使用上面的注解修饰方法的入参,(https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java?q=webcontentsobserverproxy.java


   @Override   @CalledByNative   public void viewportFitChanged(@WebContentsObserver.ViewportFitType int value) {      for (mObserversIterator.rewind(); mObserversIterator.hasNext();) {         mObserversIterator.next().viewportFitChanged(value);      }   }
复制代码


对于 viewportFitChanged()这个方法来说,通过使用 @WebContentsObserver.ViewportFittype 对入参进行修饰,在编译期检查参数合法性,在方法内部也就不再需要对参数的合法性进行检查。


  1. 再来看看 github 上的一个项目对注解的使用


public class DiagonalLayoutSettings {
@Retention(SOURCE) @IntDef({ BOTTOM, TOP, B_T}) public @interface Position { }
public final static int LEFT = 1; public final static int RIGHT = 2; public final static int BOTTOM = 4; public final static int TOP = 8; public final static int B_T = 16;
@Retention(SOURCE) @IntDef({ DIRECTION_LEFT, DIRECTION_RIGHT }) public @interface Direction { }
public final static int DIRECTION_LEFT = 1; public final static int DIRECTION_RIGHT = 2;
...
}
复制代码


用注解去修饰方法参数.


public class DiagonalLayout extends FrameLayout {
DiagonalLayoutSettings settings;

public void setPosition(@DiagonalLayoutSettings.Position int position) { settings.setPosition(position); postInvalidate(); }
}
复制代码


setPosition 这个方法,通过注解来限制参数取值范围的作用很清晰了,不再赘述.


  1. 注解在优酷上的使用


举一个例子,去年我们和 UC 有个漫画合作项目,优酷输出端侧 SDK 给 UC 集成,并且同一套 SDK 也要在优酷中使用。因此,SDK 在初始化时,需要把集成方的标识设定进来。在设计给 UC 方调用的 API 时,就使用到了注解修饰参数的方法来避免集成方对 API 的调用错误。


   public void init(@NonNull Context context, @ConfigManager.Key String key, @NonNullIAppConfigAdapter appConfigAdapter,   IUiAdapter uiAdapter, @NonNull INetAdapter netAdapter, IPayViewAdapterpayViewAdapter, IPayAdapter payAdapter,   @NonNull IUserAdapter userAdapter, IWebViewAdapter webViewAdapter,   @NonNull IComicImageAdapter imageAdapter) {      ...   }
复制代码


在这里对参数 key,使用 @ConfigManager.Key 做了限制.


注解的定义:


public class ConfigManager {
/** * 分场标识key */ public static final String KEY_YK = "yk"; public static final String KEY_UC = "uc";
@Retention(SOURCE) @StringDef({KEY_YK, KEY_UC}) public @interface Key { }
}
复制代码


优酷场对这个 API 的调用:


private void initAliComicSdk() {      AliComicSDKEngine.getInstance().init(instance, ConfigManager.KEY_YK, newIAppConfigAdapterImpl(),         new IUiAdapterImpl(), new INetAdapterImpl(), new IPayViewAdapterImpl(), null,         new IUserAdapterImpl(), new IWebViewAdapterImpl(), newIComicImageAdapterImpl());   }
复制代码

三、以指定初始容量的方式来创建集合类对象

以 ArrayList 为例,通常我们创建对象时,使用 new ArrayList<>()是最常用的方式. 当我们阅读 chromium 或是像 okhttp 这些开源代码时会发现它们在构建 ArrayList 对象时,会有意识的使用 ArrayList(int initialCapacity)这个构造方法,“刻意”使用这种方式的原因其实是值得我们细细品味一下的。


  1. 还是以 chromium 为例,摘取一段它的源码.


protected static List<String> processLogcat(List<String> rawLogcat) {   List<String> out = new ArrayList<String>(rawLogcat.size());   for (String ln : rawLogcat) {      ln = elideEmail(ln);      ln = elideUrl(ln);      ln = elideIp(ln);      ln = elideMac(ln);      ln = elideConsole(ln);      out.add(ln);   }   return out;}
复制代码


再直接看 ArrayList 两种构造方法的源码, 无参方法会默认创建 10 个元素的 list.


/*** Constructs an empty list with the specified initial capacity.** @param initialCapacity the initial capacity of the list* @throws IllegalArgumentException if the specified initial capacity* is negative*/public ArrayList(int initialCapacity) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);this.elementData = new Object[initialCapacity];}/*** Constructs an empty list with an initial capacity of ten.*/public ArrayList() {super();this.elementData = EMPTY_ELEMENTDATA;}
复制代码


两者的区别就在于,当我们往 arrayList 中添加元素发现容量不够时,它会通过调用 grow() 方法来扩容。grow()内部会以之前容量为基准,扩大一倍容量,并发生一次“耗时”的数组拷贝。因此当业务上预知 ArrayList 未来要存储大量元素时,更优雅的方式是在创建时设置初始容量,以此来避免未来内存上的频繁拷贝操作。


/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */private void grow(int minCapacity) {   // overflow-conscious code   int oldCapacity = elementData.length;   int newCapacity = oldCapacity + (oldCapacity >> 1);   if (newCapacity - minCapacity < 0)      newCapacity = minCapacity;   if (newCapacity - MAX_ARRAY_SIZE > 0)      newCapacity = hugeCapacity(minCapacity);   // minCapacity is usually close to size, so this is a win:   elementData = Arrays.copyOf(elementData, newCapacity);}
复制代码


  1. 再来看一下 okhttp 中的例子


以 request 中 headers 的 size+4 作为初始容量来创建 ArrayList 对象,因为运行时这个 result list 内部几乎每次都是要大于 10 个元素的。对于像 okhttp 这种广泛被使用的 sdk 来说,任何对代码细节的调优都是有可观收益的,同时也体现出作者对代码细节的考究。


public static List<Header> http2HeadersList(Request request) { Headers headers = request.headers(); List<Header> result = new ArrayList<>(headers.size() + 4); result.add(new Header(TARGET_METHOD, request.method())); result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.url()))); String host = request.header("Host"); if (host != null) {  result.add(new Header(TARGET_AUTHORITY, host)); // Optional. } result.add(new Header(TARGET_SCHEME, request.url().scheme())); for (int i = 0, size = headers.size(); i < size; i++) {  // header names must be lowercase.  String name = headers.name(i).toLowerCase(Locale.US);  if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name)    || name.equals(TE) && headers.value(i).equals("trailers")) {   result.add(new Header(name, headers.value(i)));  } } return result;}
复制代码


因此在我们的优酷项目中,当每次要创建 ArrayList 时,都会下意识的想想业务上在使用这个 ArrayList 时,未来大致要存储多大量级的数据,有没有必要设置它的初始容量。


上面说的这些,不仅是对 ArrayList 有效,对像 StringBuilder 等等其他集合类来说也都是类似的。代码雷同,也就不再赘述。

三、总结

对这些编码细节上的考究,很难对业务性能指标产生可量化的提升。更有意义的点在于,我们在实际开发时,避免不了要经常参考开源项目对一些功能的实现。如果不了解这些实现细节,当读到这些代码的时候,难免对细节产生疑惑,干扰我们去理解核心实现思路。反过来说,如果我充分了解了这些细节,当读到它们的时候,往往会泯然一笑,心说我知道作者为什么要这样写,赞赏作者对代码实现的优雅,对这些开源项目的作者也产生出充分的认同感。


作者 | 阿里文娱无线开发专家 观竹


2020-05-17 07:581378

评论

发布
暂无评论
发现更多内容

全面升级!Apache HugeGraph 1.2.0版本发布

百度安全

“研学测”好帮手,三步带你安装体验TDH社区开发版

星环科技

大数据 信创 科技 大模型

利用 Token Explorer 信号驾驭市场波动

Footprint Analytics

区块链 加密货币 Token 代币

英特尔实现3D先进封装技术的大规模量产

E科讯

实力认可!TDengine 在“2023 年度金猿季”两大榜单中脱颖而出

TDengine

tdengine 时序数据库

技术解读 | KunDB助力头部金融机构关键系统的Oracle国产替代

星环科技

oracle 大数据 科技 大模型

亚马逊云科技在中国推出数字孪生服务Amazon IoT TwinMaker

财见

大数据处理流程包括哪些环节

悦数图数据库

大数据处理流程

国内首个图计算标准发布,悦数图数据库通过测试

悦数图数据库

图谱

星环科技基于第五代英特尔®至强®可扩展处理器的分布式向量数据库解决方案重磅发布

星环科技

大数据 信创 科技 大模型

指标平台详解(下):第三代指标平台实现了哪些突破?

Aloudata

BI ETL 指标中台; 指标中台

(保姆级)服务器-Zabbix6.0使用Python脚本实现带图片的邮箱的报警

不在线第一只蜗牛

Python 运维 服务器

聚道云软件连接器1月新增应用/产品更新合集

聚道云软件连接器

功能更新

【第七在线】数字化转型:智能商品计划管理的核心要素

第七在线

百度安全入选2023年移动互联网APP产品安全漏洞治理十大优秀案例

百度安全

软件测试学习笔记丨JMeter使用代理录制脚本

测试人

软件测试

Footprint 的卓越资金流数据揭示加密货币的神秘叙事

Footprint Analytics

区块链 数据分析 加密货币

Python 变量?对象?引用?赋值?一个例子解释清楚

EquatorCoco

Python 前端 变量 对象 开发语言

近6成金融机构的选择!华为云GaussDB加快金融核心系统转型

华为云开发者联盟

数据库 后端 华为云 华为云GaussDB 华为云开发者联盟

图像处理-Java-指定大小压缩

alexgaoyh

Java 图像处理 图像压缩 压缩算法 指定大小

探索技术的无限可能-云原生计算2023年度奖项

字节跳动云原生计算

大数据 云原生

10个问题,教你如何使用一个事半功倍的代码托管平台

华为云PaaS服务小智

华为云 代码托管

软件测试学习笔记丨Grafana安装

测试人

软件测试

第三届“新科技 星力量” 科技实践案例评选【获奖名单】揭晓

星环科技

大数据 科技 大模型 获奖

AppLink让你的电商运营财务管理自动化

RestCloud

零代码 APPlink 自动化集成

一分钟了解Wireshark的数据包

小齐写代码

软件测试学习笔记丨性能测试体系

测试人

软件测试

产品解读 | 新一代湖仓集存储,多模型统一架构,高效挖掘数据价值

星环科技

大数据 信创 科技 大模型

实现钉钉与宁波银行对接,助力企业实现财务智能化

聚道云软件连接器

案例分享

新书上线 | 《使用 NGINX 部署和保护 Kubernetes Ingress Controller》中文版

NGINX开源社区

nginx Kubernetes API NGINX Ingress Controller NGINX Service Mesh

程序员手把手教你参与开源!拿捏!

快乐非自愿限量之名

开源 程序员 面试

从开源项目中总结出的几条编码经验_文化 & 方法_阿里巴巴文娱技术_InfoQ精选文章