写点什么

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

  • 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:581693

评论

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

来了!昇腾MindStudio全流程工具链分论坛精彩回顾,助力高效开发和迁移效率提升

科技热闻

一路同行,端点科技与海尔集团相伴十年的数字之旅

科技热闻

MySQL百万数据深度分页优化思路分析

Java你猿哥

Java MySQL 数据库 ssm 优化技术

AI 大底座,大模型时代的答卷

百度Geek说

人工智能 百度 文心一言 企业号 5 月 PK 榜

从浏览器输入域名开始分析DNS解析过程

华为云开发者联盟

开发 华为云 DNS 华为云开发者联盟 企业号 5 月 PK 榜

基于图神经网络的推荐算法

TiAmo

神经网络 算法 推荐算法

Java反射详解

timerring

Java

IT知识百科:什么是下一代防火墙和IPS?

wljslmz

防火墙 三周年连更 入侵防御系统

并发编程-ReentrantLook底层设计

Java你猿哥

Java ssm 重入锁 lock锁 底层实现原理

sysMaster: 全新1号进程实现方案,秒级自愈,保障系统全天在线

openEuler

Linux rust 操作系统 openEuler init

震撼来袭!最具中国特色的微服务组件:新一代SpringCloud Alibaba

Java 架构 微服务 Spring Cloud spring cloud alibaba

重磅发布!阿里巴巴专家亲自撰写,Dubbo 3.0 分布式实战(彩印版)

Java 分布式 微服务 dubbo

Redis和MySQL扛不住,B站分布式存储系统如何演进?

Java你猿哥

Java MySQL redis ssm kv

硬核Prompt赏析:HuggingGPT告诉你Prompt可以有多“工程”

无人之路

ChatGPT Prompt

鲸鸿动能广告接入如何高效变现流量?

HarmonyOS SDK

HMS Core

硬核!阿里最新出品架构核心场景实战手册,解决99%的架构问题

Java你猿哥

微服务 架构设计 架构师 架构场景实战 微服务实战

一周吃透Java面试八股文(2023最新整理)

Java你猿哥

Java kafka Spring Boot JVM java面试

本周日直播,全链路数据治理实践论坛开放报名

阿里云大数据AI技术

大数据 数据治理

阿里全新推出:微服务突击手册,把所有操作都写出来了|超清PDF

Java 架构 微服务 Spring Cloud spring cloud alibaba

云数据库技术沙龙|多云多源下的数据复制技术解读-NineData

NineData

MySQL Clickhouse 数据管理 多云多源 数据存取

牛掰!阿里架构师熬夜肝了一份JVM必知必会,哪里不会查哪里

Java 性能优化 JVM

DxO PureRAW轻松获取纯净无瑕疵raw照片~

真大的脸盆

Mac 图像处理 Mac 软件 Raw图像处理软件 图像编辑工具

新技术越来越多,作为程序员,我们应该怎么规划职业生涯? | 社区征文

wljslmz

三周年征文

用户分享 | Dockquery,一个国产数据库客户端的初体验

BinTools图尔兹

用户体验 国产数据库工具

k8s+Docker部署方法

Java你猿哥

Java Docker k8s ssm 架构师

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