写点什么

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

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

评论

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

AI 实时通话视频生成产品 Tavus:能听说看且有形象;Anthropic 推出 API 长文本缓存功能丨 RTE 开发者日报

声网

AI大模型快速生成题库-助力业务人效提升10+倍

京东科技开发者

电商搜索革命:大模型如何重塑购物体验?

京东科技开发者

阿里巴巴拍立淘API返回值:商品搜索与社交媒体的融合

技术冰糖葫芦

API Explorer API boy API 接口 API 测试

海外直播对网络的要求有哪些?

Ogcloud

海外直播专线 海外直播 tiktok直播 tiktok直播专线 海外直播网络

这个是我的测试编辑器的内容

网站,小程序,APP开发定制

网速慢影响海外业务?全球应用加速可以解决!

Ogcloud

网络加速 全球应用加速 企业网络加速 网络加速服务

事业-最佳实践-个人-拥抱变化

南山

TIANCHI工业 AI- 第 10 名思路分享

阿里云天池

大航杯智造扬中电力AI大赛RANK6思路总结

阿里云天池

2025杭州国际智慧城市展览会

AIOTE智博会

智慧城市展览会 智慧城市展会 智慧城市博览会

阿里云2024云栖大会,来了!

MasterInTech

Meetup 回顾:存算引擎一体化建设

Databend

事业-最佳实践-个人-做事原则

南山

社区胜于代码,我们在阿帕奇软件基金会亚洲大会聊了聊开源中间件的未来

阿里巴巴云原生

Apache 阿里云 云原生

一文讲清 API 接口的概念、设计和实现

Noah

数智重庆.全球产业赋能创新大赛总决赛极客奖队伍比赛攻略_球球君

阿里云天池

虚拟滚动 - 从基本实现到 Angular CDK

PingCode研发中心

大数据 虚拟滚动

华为账号“一键登录”能力让美团用户尽享安全便捷的登录体验

HarmonyOS SDK

HarmonyOS

独家为《白蛇:浮生》提供云渲染服务,腾讯云助力追光动画再现东方美学

ToB行业头条

腾讯云

必看!时序大模型的发展、玩家与效果对比

Apache IoTDB

公链大变局:ETH老态龙钟,SOL势如破竹

区块链软件开发推广运营

交易所开发 dapp开发 区块链开发 NFT开发 公链开发

数字货币发行项目开发基本要求及模式创建与海外宣发策略

区块链软件开发推广运营

交易所开发 dapp开发 区块链开发 NFT开发 公链开发

【YashanDB知识库】生成迁移报告失败,"报错未知类型错误异常:"

YashanDB

yashandb 崖山数据库 崖山DB

Web3 游戏周报(8.11 - 8.17)

Footprint Analytics

链游

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