写点什么

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

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

评论

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

架构师训练营:第二周学习总结

Bruce Xiong

区块链在农业领域能有什么用武之地?

CECBC

区块链技术 上链 农业链 三农

Week 02 学习总结 框架 设计原则

Z冰红茶

架构师训练营第二周感悟

张锐

极客大学架构师训练营

SharePoint 往事之:使用Bootstrap定制SharePoint网站页面

手艺人杨柳

SharePoint

docker-mcr 助您全速下载 dotnet 镜像

newbe36524

Docker netcore

依赖倒置原则

Z冰红茶

架构师训练营第二周总结

olderwei

2020/6/16 架构学习心得

架构5班杨娟Jessie

极客大学架构师训练营

架构师训练营:第二周 作业

Bruce Xiong

手撕设计原则:接口隔离

JefferLiu

面向对象 架构师 面向对象设计 面向对象设计原则

架构师训练营第二周作业

olderwei

极客大学架构师训练营

第二周作业

而立斋

依赖倒置 接口隔离原则

面向对象设计原则课后作业

周冬辉

架构师训练营第二周作业(1)

烟雨濛濛

week02 小结

Geek_196d0f

软件设计原理

而立斋

专栏

架构师实现自己架构目标工具手段-软件设计

WulalaOlala

极客大学架构师训练营

第二周学习总结

赵龙

架构师训练营第0期第二周作业

无名氏

依赖倒置原则 DIP 依赖反转原则

week02 作业

Geek_196d0f

架构师训练营第二周作业

张锐

极客大学 极客大学架构师训练营

极客时间架构课 Week02- 作业一:命题作业

yulyulcl

架构师训练营第二周作业(2)

烟雨濛濛

【架构师训练营】第2周作业

花生无翼

极客大学架构师训练营

Spring中依赖倒置原则的理解

极客李

第二周作业

赵龙

依赖倒置及Cache重构设计

架构5班杨娟Jessie

极客大学架构师训练营

北京疫情反弹 区块链怎样破解食品溯源难题?

CECBC

区块链技术 商品溯源 上链

架构师训练营第二周作业

子豪sirius

重拾依赖倒置原则(训练营第二课)

看山是山

oop 极客大学架构师训练营 依赖倒置原则 DIP

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