工作中遇到的代码反例
前言
最近一直都是 copy 工程师和 scapegoat 工程师
不过也发现了一些些问题,也用来警示自己以后写代码 尽量
考虑全面(人无完人···)
反例1-下次进入 App,回到上次退出的页面
如下图
tab-example.jpg类似知乎App的场景,顶部有一排 tab,每次记录选中的是哪一个 tab,下次进入时展示上次退出时的 tab。
原本的代码中是这样实现的(伪代码)
// 监听 ViewPager 回调
public void onPageSelected(int position) {
// 1.拿当前 fragment
fragment = getCurFragment();
// 2.通过 fragment 拿 channel
channel = getChannel(Fragment);
// 3.保存 channel(int值)
savePage(channel);
}
public void getChannel(Fragment fragment) {
if (fragment instanceof FragmentA) {
return 1;
}
// ···各个 tab
}
到这里也还好,但是当初始化的时候逻辑就很奇怪了
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
// 1.获取之前保存在本地的 channel
channel = getChannel();
// 2.通过 channel 获取 position(我第一次看的时候是懵逼的,凭啥?)
position = getPosition(channel);
setInitTabPosition(position);
}
public int getPosition(int channel) {
// 只为了表示 channel 和 position 有映射关系
switch(channel) {
case 5:
return 0;
case 7:
return 1;
}
}
这段代码就是想从本地取之前存好的 channel(也就是代表哪个 Fragment),然后通过 channel 和 position 的映射关系拿到 position,传给 ViewPager 从而让此 Fragment 对应的 tab 变为选中状态。
但是,这段代码完全没有考虑我这个后来者。如果 Fragment 的顺序永远固定,那么也就算了,但是显然是不可能的···
所以,一旦 Fragment 的数量或者位置可以主动变化,那么这段代码就没法维护了,全部乱套。
拿上面的知乎为例,假设我停在了 热榜
页面,此时我退出了 app,存储 channel = 热榜,channel 和 position 的映射关系为 channel = 热榜 & position = 2
。这时候我又进入了 app,如果 tab 是动态下发的,那么热榜前面可能多了两个 tab,此时顺序完全错乱。又或者我处于登录状态,然后退出登录,可能
会有一些 tab 需要隐藏,此时顺序也可能会错乱。
而我做的新需求,恰巧就是会有 tab 变化的(不然我也不会发现这个问题)。当我添加一个 tab 后全部错乱了时,我只能坐在电脑前凌乱···
如何修改
修改的办法也很简单,就是不存储 position 的映射关系,存储任意一个恒定不变的关系,比如 tab 的 id 或者 类型 或者 其他自定义的东西,反正就是一个 key。
而 position 怎么获取呢? 就通过遍历 tab 或者 adapter(看你怎么封装了,都放进 adapter 好一些吧),比较 key 是否相等,返回 position。
这样,无论 tab 如何变化,adapter 中的数据也一定会跟着变化,拿着 key 永远找不错。如果找不到这个 key,那只能说明数据变化后,这个 tab 被删除了,那你就得自己看业务需求让你展示哪个页面,再写一段兜底逻辑。
吸取经验
映射关系 key 的选取要考虑全面,任何情况下都不能变动,否则这个映射关系肯定存在问题。
反例2-tab 颜色渐变
还是拿知乎 app 来举例,假设顶部 tab 中有两个 tab 的文字颜色跟其他 tab 不一致,并且滑动的时候还想渐变到对应的颜色,怎么做呢?
这也恰巧就是我的需求,我要新增一个 tab,tab 文字颜色和其余 tab 不一致。
当我辛辛苦苦找到处理 tab 文字颜色的代码时,我是崩溃的
// 以下为伪代码
colors = new int[] {
resources.getColor(R.color.color1),
resources.getColor(R.color.color2),
resources.getColor(R.color.color3),
resources.getColor(R.color.color4),
resources.getColor(R.color.color5)}
public void onPageScrolled(int position, float progress, int positionOffsetPixels) {
for (int i = 0; i < tabCount; ++i) {
Tab tab = getTabAtIndex(i);
if (position == i) {
// 一坨
} else if() {
// 一坨
} else if(position == count - 1) {
// 一坨
} else {
// 一坨
}
}
}
一开始我没有细看代码,我本着快速做完需求的想法,想着删删改改来完成需求。结果这段代码看的着实头疼,虽然有很多注释,但是不找原作者感觉很难理解了···而且我改了前面后面错,改了后面前面错。
最后我看到了 position == count - 1
这种判断,我决定不使用这段代码。因为这定是为了一种特殊情况(最后一个 tab)做的逻辑,必然不可能用普适性。
不合理的点
-
color 资源没和 tab 绑定
tab 的字体颜色,应该是构造函数的参数更好一些,如果参数太多了就用构造器模式呗。目前是五种颜色,假设以后越来越多,这数组还得了嘛···而且 setColor 的时候都是 colors[0]、 colors[1] 这种代码,写着不晕? -
特殊逻辑
position == count - 1
既然是 tab 颜色变化,应该是个基础功能,而不应该有什么特殊的逻辑判断
修改
-
tab 构造函数传入一个 int[] 数组来表示各种颜色。
一个 tab 目前可以多有3个颜色。为啥?首先是选中状态的颜色,其次是未选中状态时,Fragment 的背景可能是黑色可能是白色,对应有两种未选中状态的颜色。取颜色时,通过 tab.getTabTextColor 取到数组,通过定义常量,比如 SELECTED_COLOR = 0,来表示 color 数组的第一个元素是选中态的颜色。于是代码变化为:
// 原始写法: int colors = new int[]{color0, color1} setTextColor(colors[0], colors[1], progress) // 修改后的写法: int[] colors = tab.getTextColors(); setTextColor(colors[SELECTED_COLOR], colors[UNSELECTED_COLOR], progress)
我个人认为还是比上面的要好懂很多的。
-
改掉特殊逻辑
这块就是纯粹的写逻辑了,我感觉之前的人可能就是没太理清楚逻辑(或者确实需求如此),导致存在一个特殊判断。
吸取经验
-
常量最好是定义一个有意义的名字
-
考虑好一些变量的范围,该放到哪里比较通用、比较好
-
通用组件越通用越好,特殊逻辑应该搞个接口之类的抛给外面