<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>陈池的博客</title>
    <description>陈池的博客</description>
    <link>http://blog.chenchi.cc//</link>
    <atom:link href="http://blog.chenchi.cc//feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 12 May 2026 02:49:42 +0000</pubDate>
    <lastBuildDate>Tue, 12 May 2026 02:49:42 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Chatgpt走进面试间</title>
        <description>&lt;p&gt;看了&lt;a href=&quot;https://www.infoq.cn/article/fxYffzDJGkdBofdUtsQH&quot;&gt;Chatgpt通过谷歌面试的新闻&lt;/a&gt;以后，我暗中冷笑不已：什么年代了，还用leetcode这种机械的面试题，活该如此！Chatgpt幸亏没开放大陆使用，否则落在我的手里，要你好看！&lt;/p&gt;

&lt;p&gt;&lt;!-- more --&gt;&lt;/p&gt;

&lt;h1 id=&quot;要你好看&quot;&gt;要你好看！&lt;/h1&gt;

&lt;p&gt;早年间我觉得leetcode面试还不错，有空自己也喜欢去刷，找到感觉了刷得还挺来劲。偶尔忝列面试官的时候也会出leetcode的题目，但是只用easy题目，我觉得hard题目已经不是考察编程能力了，而是考察背题能力。最常用的是这个：&lt;/p&gt;

&lt;p&gt;一个数组长度为[2n + 1]，包括n+1个数字，其中只有一个数字是单独出现，其他数字都会出现两次，例如[1, 2, 2, 1, 3] ，请找出只出现一次的数字。&lt;/p&gt;

&lt;p&gt;它的好处在于：&lt;/p&gt;

&lt;p&gt;1、很简单，写不出来直接拉黑送走；&lt;/p&gt;

&lt;p&gt;2、次优解可以从时间复杂度也可以从空间复杂度上优化，有讨论空间，能做到这一步就可以通过了；&lt;/p&gt;

&lt;p&gt;3、最优解还能做出来的应该是做过原题的，能讲清楚原理（位运算）的必然计算机基础扎实，给个高分应该；如果讲不清楚原理结合其他情况可以判断出来是作弊的也拉黑送走；&lt;/p&gt;

&lt;p&gt;4、给出最优解的还可以扩展一下，如果长度是3n+1要怎么做？&lt;/p&gt;

&lt;p&gt;这道题适合应届生，Chatgpt来做肯定能给出最优解，但是第四部的扩展应该是搞不定的，这是我自己想的，网上没有。&lt;/p&gt;

&lt;p&gt;对于工作三五年的再用这个题目就不合适了，我常用的是从知乎上看到的一个面试题：要求写一个类来表达数学上的”分数”，可以加减乘除、用作hashCode、转换成字符串和约分，比如2/3、8/13等等，不允许用辗转相除做约分。它的好处在于：&lt;/p&gt;

&lt;p&gt;1、这是一个设计题，可以考察候选人的设计能力而且可以在20分钟左右完成；&lt;/p&gt;

&lt;p&gt;2、可以考察数学基础能力，有不少英雄汉倒在加减乘除上；&lt;/p&gt;

&lt;p&gt;3、分子、分母是否是用public final、分母为0的处理、hashCode和equals上等可以看出编程习惯；&lt;/p&gt;

&lt;p&gt;4、如果用工厂方法代替构造函数应该是看过effective java的，可以展开；&lt;/p&gt;

&lt;p&gt;5、加减乘除需要两个分母相乘，这时候的溢出处理就比较费脑子了；&lt;/p&gt;

&lt;p&gt;6、toString的时候边界条件处理上也可以看出来细心程度。&lt;/p&gt;

&lt;p&gt;这个题目交给Chatgpt的话，辗转相除应该会做，但是其他应该是不行的，毕竟这是一个主观题。&lt;/p&gt;

&lt;h1 id=&quot;看得目瞪口呆&quot;&gt;看得目瞪口呆&lt;/h1&gt;

&lt;p&gt;我注册了一个chatgpt以后感觉很好用，想起来那个新闻之后觉得chatgpt是一个检查面试题好坏的标准：如果chatgpt都能做出来的面试题，肯定不是什么好面试题，好的面试题是chatgpt做不出来的面试题。&lt;/p&gt;

&lt;p&gt;为了验证一下我自己的这两道题有多好，我把它出给了chatgpt，不然怎么：“要你好看！”。&lt;/p&gt;

&lt;h2 id=&quot;第一题的扩展&quot;&gt;第一题的扩展&lt;/h2&gt;

&lt;p&gt;第一题不用说，原题直接给出了最优解，这是意料之中的。&lt;/p&gt;

&lt;p&gt;但是我对自己的扩展题目还是很有信心的，没想到的是……chatgpt给出来了最优解，必须S。&lt;/p&gt;

&lt;p&gt;题目：一个数组长度为[3n + 1]，包括n+1个数字，其中只有一个数字是单独出现，其他数字都会出现三次，例如[1, 2,  2,1, 1, 2, 3] 。请找出只出现一次的数字。&lt;/p&gt;

&lt;p&gt;回答&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;findSingleNumber&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitMask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;--)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitMask&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;bitSum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;bitMask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitMask&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bitSum&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;设计分数&quot;&gt;设计分数&lt;/h2&gt;

&lt;p&gt;分数题目做得中规中矩，但是实际面试当中能做到这个程度的大约10%，考虑到编程速度可以给个B+。toString写得很好，分子为0的判断其实我也没想到；构造函数通过抛出异常避免分母为0做得也很好，不过这个应该都能想到。&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IllegalArgumentException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;分母不能为0&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;subtract&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;multiply&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;divide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Fraction&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;simplify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gcd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Fraction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;gcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;gcd&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;分数题目当中，一个特殊的感受是chatgpt的题目能做多好，基本上和题目出的多好有很大关系。toString的时候本来只有简单的分子/分母形式，但是我增加了描述以后，就写得非常好。但是溢出情况上无论我怎么描述，它始终没有做任何处理，不知道为什么，溢出可以通过加减乘除的时候先约分一轮来缓解的。&lt;/p&gt;

&lt;h2 id=&quot;时代变了大人&quot;&gt;时代变了，大人！&lt;/h2&gt;

&lt;p&gt;Chatgpt的两道题表现，我给A，通过。&lt;/p&gt;

&lt;p&gt;面试Chatgpt的感受是，它的面试表现，其实取决于面试官的水平，面试官水平高，表达清楚，Chatgpt就做得好。它在谷歌拿了L3百万年薪，那么面试它的人水平应该不低。从这个角度来看，本文的这场面试，与其是说我在给Chatgpt出题，倒不如说是Chatgpt在给我出题。&lt;/p&gt;

&lt;p&gt;当Chatgpt走进面试间……&lt;/p&gt;

&lt;p&gt;如果它是候选人，那么这只是一场文字游戏而已；&lt;/p&gt;

&lt;p&gt;如果它是面试官，那么意味着程序员面试的时代变了：可以让候选人来使用Chatgpt写程序，这需要候选人思路清晰、表达准确，Chatgpt才能写出正确的程序来，而且这个过程是高效、客观的。&lt;/p&gt;

&lt;p&gt;将来，阿不，就是现在，可以考虑让Chatgpt走进面试间，至少代替面试的笔试部分，这也许是程序员面试的一种新形式。&lt;/p&gt;

</description>
        <pubDate>Sat, 11 Feb 2023 02:37:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2023/02/11/chatgpt_interview.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2023/02/11/chatgpt_interview.html</guid>
        
        <category>Chatgpt</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>MPAndroidChart：图表库的八卦、定制和脑洞</title>
        <description>&lt;p&gt;MPAndroidChart是一个广泛使用的图表开源库，本文的重点在于介绍它的图表绘制深度定制方法及相关讨论，也有一些八卦信息和脑洞，交互部分了解不多不讨论，对于配置参数、接入方法因为已经有很多文章介绍，不再赘述。&lt;/p&gt;

&lt;p&gt;&lt;!-- more --&gt;&lt;/p&gt;

&lt;h1 id=&quot;简介&quot;&gt;简介&lt;/h1&gt;

&lt;p&gt;MPAndroidChart是Android最好和使用最广泛的图表开源库，它还有有一个iOS孪生兄弟Charts，也是最优秀的图表开源库之一。它们两个的关系确实很像孪生兄弟：Api接口对齐程度达到95%，而且同步开发，内部类结构甚至类名上也基本对齐，以至于Android和iOS两端任意一端的图表解决方案，都可以不加修改的拿到另外一端去执行。
下面是它们的Github：&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/PhilJay/MPAndroidChart&quot;&gt;https://github.com/PhilJay/MPAndroidChart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/danielgindi/Charts&quot;&gt;https://github.com/danielgindi/Charts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://javadoc.jitpack.io/com/github/PhilJay/MPAndroidChart/v3.1.0/javadoc/&quot;&gt;MPAndroidChart Api说明&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://weeklycoding.com/mpandroidchart-documentation/&quot;&gt;MPAndoridChart详细文档&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个网站看起来非常山寨，它确实是MPChart的官方文档网站，对于如何上手以及各个Api的用法解释得非常准确详细。由于iOS的Charts和MPAndroidChart在Api层面几乎完全对齐，Charts也可以使用这个文档。就在Charts的GitHub上还提到“Study the Android version’s Documentation-Wiki”。&lt;/p&gt;

&lt;p&gt;如何使用、配置、完成图表开发，参考上述官方文档即可，网上许多MPAndroidChart详解之类的文章，很少超过对对Api的翻译水平，并不如自己尝试一下了解的更快更详细。&lt;/p&gt;

&lt;p&gt;如果是比较标准的图表需求用这个库可以很容易的做出来，一般只要使用一些配置就可以了，即便是很个性化的需求，用这个库实现起来也相对容易。&lt;/p&gt;

&lt;h1 id=&quot;八卦&quot;&gt;八卦&lt;/h1&gt;

&lt;p&gt;MPAndroidChart的作者Philipp Jahoda，是一名奥地利人，生活在林茨（Linz），可能现在生活在维也纳，他是一个重度骑行爱好者。技术栈非常广，从Java后端、数据库，到前端的Swift、Android/Kotlin、RN等都有很多经验。还是一名企业家，经常去学校做演讲，很可能在当地也是一位小有名气的人物。如果生活在中国大概可以加入市政协或者担任县工商联副主席级别的职务。&lt;/p&gt;

&lt;p&gt;大概是2015年或者更早开始了MPAndroidChart的开发，大概同时间也在开发Butleroy应用，这个应用在iOS上也可以搜到。有可能是他们在做ButleroyApp的时候，需要图表功能于是就开发了这样一个库。&lt;/p&gt;

&lt;p&gt;2018年作为四个联合创始人之一的Butleroy应用公司获得了数百万欧元的投资，他们公司名字也叫做Butleroy，是基于机器学习的数字助理，看起来是一个Outlook工具，帮助制定日程和提醒的。&lt;/p&gt;

&lt;p&gt;2019年，ButleroyApp跻身进入iOS上的付费应用top榜，现在还能在AppleStore下载到，要花60块钱，今天我看了看似乎缺乏打理，目前的平均评分只有3，精选评论标题是“不推荐”，但是Mac版本的评分更好一些，也许这个应用更适合桌面使用。它的更新记录停止在2020年。看来这次创业，至少没有大获成功。&lt;/p&gt;

&lt;p&gt;MPAndroidChart版本停留在2019年，之后没有进一步的更新，但是小的更新持续到现在，对应的iOS版本也在更新。iOS版本的MPAndroidChart，Charts似乎也是和MPAndroidChart同步开发的，他们的API对齐程度令人惊讶，我在一个规模更小的项目上遇到了对齐Api的需求，但是非常困难而且对齐程度远远不如MPAndroidChart&amp;amp;Charts。&lt;/p&gt;

&lt;p&gt;现在供职于ahoikapptn，可能是联合创始人，这应该是一家外包公司，主要服务大型客户，提供从Ui设计到最终App上架的全套服务。但是也有自己的App产品，还出售服装，我也不清楚这些服装是周边产品还是他们本来就出售服装。&lt;/p&gt;

&lt;p&gt;然而Charts的作者来自以色列，很难想象一个奥地利人和以色列人远隔万里是怎么做出来一个Api基本对齐的两套工具的？我们当时挤在一个办公室里面都没有把Api对齐到他们的程度。&lt;/p&gt;

&lt;p&gt;这个开源项目持续好几年也总共只收到了捐赠共计 $2100，看来靠开源养活自己是不太可能的，程序员需为五斗米折腰。&lt;/p&gt;

&lt;p&gt;在&lt;a href=&quot;https://stackoverflow.com/questions/45011475/mpandroidchart-how-to-listen-to-piechart-rotation/45547864#45547864&quot;&gt;这个问题&lt;/a&gt;当中，作者本人的回答没有被采纳，反而是其他人给出了不同的答案被采纳了。&lt;/p&gt;

&lt;h1 id=&quot;定制&quot;&gt;定制&lt;/h1&gt;

&lt;p&gt;就我使用MPAndroidChart的经验来看，定制化的图表开发，并不需要修改MPAndroidChart内部的SDK，只要使用它的参数设定加上现有的扩展能力就足够了。如果你觉得自己的需求是必须修改SDK才能实现的，那么很可能是对代码了解不够。这意味着MPAndroidChart的作者对图表有非常深刻的认识，在隐藏细节的同时，也开放了足够的定制化空间。&lt;/p&gt;

&lt;p&gt;在快手创作者版开发的过程中，我还是修改了SDK的代码，但是实际上后来对MPAndroidChart有更多了解以后，才发现是可以不必修改的。&lt;/p&gt;

&lt;h2 id=&quot;举例&quot;&gt;举例&lt;/h2&gt;

&lt;p&gt;首先，我们看&lt;a href=&quot;https://stackoverflow.com/questions/72805567/mpandroidchart-or-another-ios-swift-chart-tool-use-gradient-based-on-3rd-varia/72809951#72809951&quot;&gt;一个例子&lt;/a&gt;，怎么对MPAndroidChart进行扩展，实现配置项无法支持的需求开发。唯一的回答是需要修改SDK代码，实际上并不需要。&lt;/p&gt;

&lt;h3 id=&quot;需求&quot;&gt;需求&lt;/h3&gt;
&lt;p&gt;让折线图展示成为渐变彩色，如下：
&lt;img src=&quot;/assets/img/2023-01-18-chart_1.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;解决方案&quot;&gt;解决方案&lt;/h3&gt;
&lt;p&gt;在现有的Api支持当中，修改折线颜色是没有问题的， 但是没有修改成彩色的，在原来的回答当中，是直接修改了SDK里面负责绘制折线的代码，设置为渐变色解决的。&lt;/p&gt;

&lt;p&gt;如果不修改SDK，可以通过这样的方式修改：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;从LineChart继承一个CustomLineChart;&lt;/li&gt;
  &lt;li&gt;从 LineChartRenderer继承一个CustomLineChartRenderer;&lt;/li&gt;
  &lt;li&gt;CustomLineChart重写init方法，把mRenderer设置为CustomLineChartRenderer；&lt;/li&gt;
  &lt;li&gt;CustomLineChartRenderer的drawLinear方法给Painter设置渐变色彩：&lt;/li&gt;
&lt;/ol&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;drawLinear&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Canvas&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ILineDataSet&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;Shader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LinearGradient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;RED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BLUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Shader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TileMode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIRROR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;mRenderPaint&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setShader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;drawLinear&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;就可以实现彩色折线效果，下图略鬼畜，不过调优以后应该会好看一些：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2023-01-18-chart_2.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;图表的组成部分&quot;&gt;图表的组成部分&lt;/h2&gt;

&lt;p&gt;一个图表，大致可以划分成为下面三部分：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2023-01-18-chart_3.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;两个红框：坐标区域&lt;/p&gt;

&lt;p&gt;蓝框：绘制区域&lt;/p&gt;

&lt;p&gt;绿框：图例区域&lt;/p&gt;

&lt;p&gt;还有标签和十字线、图表交互等，不影响讨论和结论，就从略了。&lt;/p&gt;

&lt;h2 id=&quot;mpandroidchart基本类图&quot;&gt;MPAndroidChart基本类图&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2023-01-18-chart_4.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Chart类是图表的基类，它的几个成员分别对应了图表的几个区域：&lt;/p&gt;

&lt;p&gt;● mData -&amp;gt; 图表数据&lt;/p&gt;

&lt;p&gt;● mRenderer -&amp;gt; 蓝框绘制区域&lt;/p&gt;

&lt;p&gt;● mXaxis -&amp;gt; 红框坐标区域&lt;/p&gt;

&lt;p&gt;● mLengendRenderer -&amp;gt; 绿框图例绘制区域&lt;/p&gt;

&lt;p&gt;各种图表都是Chart的子类，分别在子类当中实现本图表的特有功能，并且可以通过set/get方法把mRenderer、mXaxis等成员替换为自己需要的实际类型。比如折线图的类就是LineChart，它的mRenderer是LineChartRenderer，其他还有柱状图是BarChat等，不在类图当中一一标记。&lt;/p&gt;

&lt;p&gt;可见，MPAndroidChart的成员和类继承关系，和图表的区域划分基本一致。&lt;/p&gt;

&lt;h2 id=&quot;定制开发图表&quot;&gt;定制开发图表&lt;/h2&gt;

&lt;p&gt;那么定制开发图表的方法就很清楚了，需要定制化哪部分，就找到对应的类，可以用组合方式替换实现就用组合方式，不能用组合方式，就用继承方式，把自己的定制代码从外部设置给Chart。&lt;/p&gt;

&lt;p&gt;例子里面，就是要定制折线绘制部分，那么对应的类是Renderer，通过继承-组合方式替换以后就解决，类图如下（橙色是新继承出来的子类）：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2023-01-18-chart_5.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;比如说希望定制化坐标轴：&lt;/p&gt;

&lt;p&gt;● 首先从现有的方法和属性里面找能否满足需求；&lt;/p&gt;

&lt;p&gt;● 如果还不可以，就要在对应的Chart类（比如BarChart）当中找到坐标轴的实现类（比如XAxis）；&lt;/p&gt;

&lt;p&gt;● 然后通过继承一个子类的方式，在子类里面修改出需要的效果；&lt;/p&gt;

&lt;p&gt;● 把这个子类替换为Chart类实际使用的坐标轴实现类&lt;/p&gt;

&lt;p&gt;通过上述4个步骤可以完成高度定制化的图表需求。&lt;/p&gt;

&lt;h2 id=&quot;细节定制修改&quot;&gt;细节定制修改&lt;/h2&gt;

&lt;p&gt;注意到我们前面的代码是很简单的：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;drawLinear&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Canvas&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ILineDataSet&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;Shader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LinearGradient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;RED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;BLUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Shader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TileMode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MIRROR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;mRenderPaint&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setShader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;drawLinear&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;所有的修改都会这样简单吗？并不会的，就是这个渐变线折线图的例子，实际上mRenderPaint的使用是需要满足一定条件的，所以现在的做法可能会造成性能问题。如果确实有一大段代码的复杂逻辑当中的一两行需要修改，还可以做到不修改SDK吗？&lt;/p&gt;

&lt;p&gt;CustomLineChartRenderer.drawLinear确实是一大段代码，选择设置颜色相关逻辑的流程图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2023-01-18-chart_6.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;题目要求的彩色折线效果，需要把黄色流程框里面的“设置画笔颜色”替换成“设置画笔为彩色渐变”。对于这种非常定制化的修改，我以前认为，只能通过SDK对外暴露接口来满足需求，否则就只能修改SDK代码了。然而设置画笔颜色的用接口暴露了出去，绘制折线图要不要暴露？判断坐标是否位于范围以内要不要暴露？是不是每一步逻辑都需要作为接口暴露出去？&lt;/p&gt;

&lt;p&gt;任何一个SDK都不可能通过接口穷尽所有的定制化需求，这个思路其实和设置属性是没有区别的，在维护上、使用上都存在无法解决的成本问题，只可能选择典型重要的属性和接口暴露。&lt;/p&gt;

&lt;p&gt;但是在MPAndroidChart当中，数据往往定义为protected类型，那么就提供了一个新的思路是：子类把整个一段函数复制过来修改定制。&lt;/p&gt;

&lt;p&gt;也有private的数据影响我们复制代码，比如LineChartRenderer.drawLinear就使用了一个private类型的mLineBuffer，导致我们不能复制代码。仔细观察一下mLineBuffer的使用范围，会发现它只在drawLinear当中用到，那么它其实不影响，只要把这个数据结构一起复制过来就可以了。&lt;/p&gt;

&lt;p&gt;作者的编程习惯是非常好的，这样写出来的代码本身就具备很高的重用性，确保我们可以在复杂情况下，可以通过复制父类代码并且修改的方式，完成自己需要的定制工作。&lt;/p&gt;

&lt;p&gt;做到这一步，应该没什么定制化需求还是要修改SDK的了。&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;MPAndroidChart的定制开发可以通过如下方式实现：&lt;/p&gt;

&lt;p&gt;● 首先使用开放出来的属性和接口设置；&lt;/p&gt;

&lt;p&gt;● 其次找到需要定制化的部分对应类，继承子类，通过重写函数在子类当中实现定制功能，然后设置回到Chart；&lt;/p&gt;

&lt;p&gt;● 如果定制化内容复杂，那么可以把基类里面的函数复制到重写函数里面修改逻辑实现。&lt;/p&gt;

&lt;p&gt;其中最后一条对我来说挺新鲜的，以前也没见到有什么书上或者其他类库用这种做法。&lt;/p&gt;

&lt;h1 id=&quot;疑问&quot;&gt;疑问&lt;/h1&gt;

&lt;p&gt;图表toB的业务，往往对定制话没有特殊要求，一般只要通过属性和设置就可以满足需求，但是toC的业务，例如健身App、个人账目等，会有各种各样的奇特图表需求从产品、设计师的脑洞里喷涌而出，这样的需求，MPAndroidChart都可以满足吗？&lt;/p&gt;

&lt;h2 id=&quot;mpandroidchart可以满足未知的图表需求吗&quot;&gt;MPAndroidChart可以满足“未知”的图表需求吗？&lt;/h2&gt;

&lt;p&gt;我自己的经历是不用修改SDK就可以满足需求的，但是样本数量太少了。所以我去stackoverflow的MPAndoridChart提问区回答上面的问题，而且尽量不去修改SDK。刷了一百多分以后，确实这些问题都不需要修改SDK就可以解决，在这个观察样本量基础上说MPAndroidChart是可以满足未知的图表需求，是很可靠了。&lt;/p&gt;

&lt;h2 id=&quot;mpandroidchart是怎么未卜先知的&quot;&gt;MPAndroidChart是怎么“未卜先知”的？&lt;/h2&gt;

&lt;p&gt;我以前参加过SDK的开发，当时一个困惑就是，怎么满足“未知”的需求？会不会业务方一个需求提上来，发现还得改SDK才能满足就不好看了。&lt;/p&gt;

&lt;h3 id=&quot;常用做法是&quot;&gt;常用做法是：&lt;/h3&gt;

&lt;p&gt;首先，能力上划分需求边界，是我们SDK的能力范围以内才支持，能力范围以外本来就不应该支持，一个分享SDK，常见分享能力和扩展是要支持的，但是不能让它支持登录。&lt;/p&gt;

&lt;p&gt;其次，能力以外的功能确实变化很多，比如分享面板是界面上的内容，设计师甚至可以要求做一个彩虹样式的面板，作为SDK怎么支持？这些东西就使用依赖倒置，交给业务方自己实现。&lt;/p&gt;

&lt;p&gt;这个做法不算错，但是它实际上没有帮助业务方处理“未知”的需求，而是不接收“未知”的需求。&lt;/p&gt;

&lt;h3 id=&quot;mpandroidchart的做法是&quot;&gt;MPAndroidChart的做法是：&lt;/h3&gt;

&lt;p&gt;从前面“定制”部分可以知道，图表的各个功能做出了功能正交的划分，各个功能真正做到高内聚、低耦合。如果有未知的需求不能用SDK提供的设置方式满足，那么就可以通过替换功能组件的方式实现自己的需求即可。比如想改变图例的方式，可以用一个继承的类替换掉mLengendRenderer。代码习惯很好，比如很多数据结构定义为protected，确保子类可以访问，方便继承以后修改对应的方法和逻辑，给了业务方很大的灵活性。&lt;/p&gt;

&lt;p&gt;图表也有这个基础，它反正就是在二维平面上绘制，划分成几个区域来解决也是很方便的，需要考虑的主要逻辑问题就是绘制顺序，不过在我回答的问题里面也没有出现绘制顺序导致的问题。&lt;/p&gt;

&lt;p&gt;但是无论如何，对图表的深入理解和正确划分都是一切的基础。&lt;/p&gt;

&lt;h2 id=&quot;为什么强调不修改sdk&quot;&gt;为什么强调不修改SDK？&lt;/h2&gt;

&lt;p&gt;如果你的应用肯定只有一个图表，那么修改SDK是没问题的。&lt;/p&gt;

&lt;p&gt;如果有第二个或者更多图表，就不要修改SDK了，一方面通过继承-组合方式是很容易避免修改SDK的，另外一方面，对SDK的修改可能影响到其他图表的绘制和开发。&lt;/p&gt;

&lt;h2 id=&quot;mpandroidchart和charts是怎么对齐api的&quot;&gt;MPAndroidChart和Charts是怎么对齐Api的？&lt;/h2&gt;

&lt;p&gt;已经询问两位作者，还没回复，有了回复再更新。&lt;/p&gt;

&lt;p&gt;我怀疑是Charts直接复刻MPAndroidCharts的，这要Charts的开发人员对iOS和Android都很熟悉，而且很认可MPAndroidCharts的设计、实现理念才行。&lt;/p&gt;

&lt;h1 id=&quot;脑洞&quot;&gt;脑洞&lt;/h1&gt;

&lt;p&gt;一个问题是使用者不知道自己的需求怎么才能在MPChart上满足，怎么设置，怎么修改，也许ChatGpt可能帮帮忙。&lt;/p&gt;

&lt;p&gt;这样的问题不仅仅是MPAndroidChart存在，其实也普遍存在于任何SDK或者框架当中，怎么在设计框架的时候尽量简化，让使用者更方便的自己知道如何使用，怎么让使用者不需要关心内部实现细节，也是挺困难的，好像没有哪个SDK或者框架能做到。&lt;/p&gt;

&lt;p&gt;他们提供的demo，如果能点一下就提示出来这是什么类什么方法实现的就好了，定位代码方便很多，现在需要自己断点调试还是不太方便。&lt;/p&gt;

&lt;p&gt;MPAndroidChart里面大量定义protected级别的数据我还是第一次遇到，这种做法很方便通过继承定制化特别的函数，但是在其他的类库或者一些设计模式的书里面，也没有提到这样的用法，不知道是作者自己发明的，还是哪里参考的，或者这本来就是一种特别普通的做法不值得总结出来？&lt;/p&gt;

&lt;p&gt;Android的FrameWork、古代的MFC/VC++、VCL/Delphi都是没有这种做法的，Swift、Qt应该也是没有的，对于子类修改父类逻辑没有特意提供支持。它们都是对界面库的封装，显然也不允许修改SDK，它们面对的问题比MPAndroidChart来说应该是有过之无不及，它们的设计都经受住了考验证明是成功有效的。所以，会不会还有一种可能是这种通过定义protected方便继承定制化函数内部逻辑的做法，其实只是无奈之举，并不算最好的做法？也就是我们有可能设计出来一个更好的图表库，它既可以不修改SDK就满足未知需求，也不用提供protect数据的方式要求使用者自己修改逻辑？&lt;/p&gt;

&lt;p&gt;上面一众Ui库的架构师比如Anders Hejlsberg肯定比MPAndroidChart作者Philipp Jahoda更加高明，MPAndroidChart作者Philipp Jahoda又比我高明，那么我来思考这个问题明显在僭越，不思考了，上帝已经笑坏肚子。&lt;/p&gt;

</description>
        <pubDate>Wed, 18 Jan 2023 22:37:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2023/01/18/chart.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2023/01/18/chart.html</guid>
        
        <category>MPAndroidChart</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>池西木年谱</title>
        <description>&lt;p&gt;池西木（1922年5月22日-2012年9月22日），原名池熙沐，湖北省安陆人，父亲池继业，母亲殷氏。教授级高级工程师，河南省骏马化工集团总工程师，河南省政协委员。&lt;/p&gt;

&lt;p&gt;年谱：
 &lt;!-- more --&gt;&lt;/p&gt;

&lt;p&gt;1930-1934    就读安陆进文小学，之前到过武汉呆了几个月，寒假时随父亲去住到暑假才回安陆正式进小学；&lt;/p&gt;

&lt;p&gt;1934-1938    考入安陆初中，原名湖北省立十中。进校那年换了牌子和校长，只办三年，之后合并入湖北省立联合中学巴东火烽初中分校；&lt;/p&gt;

&lt;p&gt;1938-1941    考入巴东东渡口湖北省联合中学巴东高工分校，在应用化学科学学习三年；&lt;/p&gt;

&lt;p&gt;1941.7 - 1941.9    参加湖北省干训团举办的初期学生集中训练团举办的暑期学生集中训练；&lt;/p&gt;

&lt;p&gt;1941.9-1942.5    奉命前往湖北咸丰清水塘兽疫血清厂实习；&lt;/p&gt;

&lt;p&gt;1942.5 -1942.10    奉命前往巴东火车油厂筹建处报道正式工作，同年拿到高工分校发给的毕业证书；&lt;/p&gt;

&lt;p&gt;1942.11-1943.10    在重庆南岸兵工署四公里炼油厂工作；&lt;/p&gt;

&lt;p&gt;1943.11-1944.5    考入教育部特设江津白沙大学先修班理工五组；&lt;/p&gt;

&lt;p&gt;1944.5 - 1944.10    在重庆娄浂沟交通部第4炼油厂工作；&lt;/p&gt;

&lt;p&gt;1944.10 -1946.5    被保送免试进重庆大学化工系学习；&lt;/p&gt;

&lt;p&gt;1944.5 - 1944.11    短期工作，在重庆南岸下龙门浩一家私营酒精厂（光大酒精厂）；&lt;/p&gt;

&lt;p&gt;1946.11    开始在重庆千厮门水巷子第一税指所找到一份“雇员”工作记账，兼职到1948年；&lt;/p&gt;

&lt;p&gt;1948.8    从重庆大学毕业；&lt;/p&gt;

&lt;p&gt;1948.8 -1949.9    跟几位老乡合作办了一个肥皂厂，时间不长倒闭了；&lt;/p&gt;

&lt;p&gt;1949年与徐则佳女士结婚；&lt;/p&gt;

&lt;p&gt;1949.11月底重庆解放了；&lt;/p&gt;

&lt;p&gt;1949.12    去重庆军管会登记要求工作；&lt;/p&gt;

&lt;p&gt;1950.3.15    被编入第一批赴东北沈阳工作人员，在沈阳由东北工业部转介到沈阳铁西尚武街东北化工局计划处生产计划科；&lt;/p&gt;

&lt;p&gt;1953年3月    奉命调到北京中央重工业部的中央化工局在计划处管生产计划；&lt;/p&gt;

&lt;p&gt;1955年    被调到中央化工部的设计院；&lt;/p&gt;

&lt;p&gt;1961年3月    被下放到兰化公司合成氨厂；&lt;/p&gt;

&lt;p&gt;1963年    被调到北京中央化工部的基建总局工程技术处；&lt;/p&gt;

&lt;p&gt;1969.6 - 1970.11    被下放河南太康五·七干校参加农业劳动；&lt;/p&gt;

&lt;p&gt;1970年11月    奉命调工作支援地方去驻马店地区化肥厂报道；&lt;/p&gt;

&lt;p&gt;1971年    艰苦创业从事氨触媒生产研制；&lt;/p&gt;

&lt;p&gt;1975年    得到河南省通知要正式建一个氨触媒生产工厂，完成了氨触媒生产研制任务；&lt;/p&gt;

&lt;p&gt;1980年    氨触媒独立成为一个厂子，随后又跟化肥厂合并为一个厂；&lt;/p&gt;

&lt;p&gt;1988年    退休，时年66岁；&lt;/p&gt;

&lt;p&gt;1996年11月22日老伴徐则佳女士去世，享年75。&lt;/p&gt;

&lt;p&gt;2012年9月22日去世，享年90，遗体尊照本人遗愿，捐献给北京协和医科大学，做教学研究用。&lt;/p&gt;

</description>
        <pubDate>Sun, 22 May 2022 18:37:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/2022/05/22/chiximu.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/2022/05/22/chiximu.html</guid>
        
        <category>池西木</category>
        
        
        <category>读书笔记</category>
        
      </item>
    
      <item>
        <title>走近科学：排查业务错误的“暗物质”成分</title>
        <description>&lt;p&gt;虽然我们有完善的数据埋点和错误日志收集，但是有时候用户报告的问题有可能正好没有被埋点和日志覆盖，这不仅给我们解决问题造成困难，也无法估计问题影响范围，本文结合实例和时事要闻对此类问题的解决和估算做了分析和展望。[原文链接][原文链接]
 &lt;!-- more --&gt;&lt;/p&gt;

&lt;h3 id=&quot;什么是业务错误的暗物质成分&quot;&gt;什么是业务错误的”暗物质“成分？&lt;/h3&gt;
&lt;p&gt;我们判断业务的错误率一般是通过埋点，遇到异常或者失败的时候上报一个错误状态以及错误信息，这样可以监控到业务的失败率。应该说，绝大部分业务错误通过这种方式是可以检测到的。埋点就像警戒雷达一样随时监控错误，一旦发生就告警通知我们修复。&lt;/p&gt;

&lt;p&gt;但是难免会出现意外的情况，业务发生了错误但是并没有上报埋点。这样的错误就像暗物质一样，它只对用户发生作用，不和我们的埋点、日志发生作用：悄悄得给用户制造一场意外，默默得退回到一个未知角落，静静得等待时机下一次作妖，我们的警戒雷达却无法发现它。因为这种“暗物质错误”不会报告埋点，我们也不能通过埋点知道它是否发生，不掌握它发生了多少次，只能依靠少数热心用户主动报告，这些热心用户很珍贵，他们就是我们的眼睛。&lt;/p&gt;

&lt;p&gt;下图是一位特斯拉热心用户在主动报告问题：
&lt;img src=&quot;/assets/img/2021-04-25_unknown_pic1.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;物理上的暗物质，是和引力场发生作用，但是不和电磁场发生作用的物质，因为人类观察宇宙的主要手段就是电磁波，所以被叫做暗物质。和这类不反应在埋点上但是反应在用户体验上的错误就很像。&lt;/p&gt;

&lt;h3 id=&quot;暗物质错误的危害&quot;&gt;“暗物质错误”的危害&lt;/h3&gt;
&lt;p&gt;特斯拉目前遇到的问题，疑似是这种错误：用户反馈了错误，但是他们排查埋点没有发现问题点，也不能用自己的设备重现问题，于是公布了错误发生前半小时埋点发动大家一起查：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/2021-04-25_unknown_pic2.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;就我遇到的情况，有三种原因：有的是因为吞掉异常导致，当然就无法检测到错误现象（被我们吞了）；有可能是第三方SDK导致，比如第三方SDK本来失败了， 但是返回“成功”给快手，这样自然也会制造一个无法被检测到的错误；也有可能是手机系统问题，虽然比较罕见，但是也不是不可能，我们最近遇到的一个问题就是vivo Android 11手机上出现的，因为是系统问题，应用层面基本没机会捕获到这样的错误。当然不排除其他原因，好比特斯拉遇到的应该就不是这三种之一。&lt;/p&gt;

&lt;p&gt;收到用户上报以后，如果能及时定位和解决问题就还好，然而这类问题也只有上述的第一种情况还可能相对容易及时定位，另外两种情况都需要和第三方反复沟通，甚至需要拿到问题现场获取日志，这个时候除了解决Bug的超期压力以外，我们还面临着另外一个压力就是，我们不知道这种错误在每天好几亿用户里面占比多少，无法评估这类错误的严重程度。&lt;/p&gt;

&lt;p&gt;想一下，如果你的手机偶尔重启，你会联系厂商报告错误吗？大概心中暗下决心：换手机也换牌子！&lt;/p&gt;

&lt;p&gt;很久以前我收到过一个用户反馈分享错误，还好这个错误通过查埋点可以查到失败信息和函数调用堆栈，它仍然在监控中只是没有设置在报警范围里面而已。这个用户报告的错误是每天发生N次，已经持续两星期，也就是说一个主动上报用户背后，可能是14*N次错误（pv），具体数字恕我保密了。这样可以在埋点查到的错误，总是有办法处理的，后来我们调整了APM监控，增加了集中出现的错误的监控，把这类问题纳入了警戒雷达的视线。&lt;/p&gt;

&lt;p&gt;从这个角度来说，“暗物质错误”的最大危害就是我们不知道它有多严重。&lt;/p&gt;

&lt;p&gt;就像物理学的暗物质可以通过电磁波以外的手段探知一鳞半爪，这样的“暗物质错误”也有可能通过埋点数据分析，至少能做到管中窥豹。&lt;/p&gt;

&lt;h3 id=&quot;捕获已知暗物质错误的具体案例&quot;&gt;捕获已知“暗物质错误”的具体案例&lt;/h3&gt;
&lt;p&gt;就我遇到的一个案例来说，用户反馈了错误，虽然不能用埋点直接排查出来，但是根据现有的埋点也是有可能估计出大致的数量的。&lt;/p&gt;

&lt;p&gt;问题的具体现象是用户执行QQ或者QZone分享的时候，没有拉起QQ，反而提示了”邀请成功”。&lt;/p&gt;

&lt;p&gt;我们通过添加日志给用户重现回捞以后确认是QQ SDK的问题，没有成功拉起QQ并且直接返回了取消回调。在埋点的表现上看，这种错误隐藏在了普通的取消事件里面。&lt;/p&gt;

&lt;p&gt;这个问题在排查过程中联系了QQ的技术支持人员，但是由于用户那里的QQ日志回捞失败，导致无法进一步推进。我们观察QQ和QZone的相关分享数据，在用户报告前后没有发生可信的数据波动，可以排除这个问题是刚刚发生并且有较大影响的可能，但是有没有可能这个问题从一开始就存在呢？它占比是多少呢？&lt;/p&gt;

&lt;p&gt;更为严峻的是，这个用户提到，他的很多朋友也有一样的问题。&lt;/p&gt;

&lt;p&gt;从正面的分析是不可能得到答案的，出错的取消和正常的取消对我们来说都是来自QQ SDK的取消回调，不会标记出来“这个取消是错的，请关注”，“这个取消是用户的，不用关注”。
&lt;img src=&quot;/assets/img/2021-04-25_unknown_pic3.jpg&quot; alt=&quot;JPG&quot; /&gt;
&lt;img src=&quot;/assets/img/2021-04-25_unknown_pic4.jpg&quot; alt=&quot;JPG&quot; /&gt;
&lt;img src=&quot;/assets/img/2021-04-25_unknown_pic5.jpg&quot; alt=&quot;JPG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;不过我们可以观察一下龌龊的金条有什么特征：它是QQ SDK直接返回的，用户没有做任何操作，界面闪了一下就消失了。&lt;/p&gt;

&lt;p&gt;那么，这样的取消，速度应该很快，比普通的用户亲手点取消按钮的取消分享要快很多。而且我们添加日志也证明这一点，用户那里上报的埋点可以计算出来从启动分享到取消只有2.15秒。&lt;/p&gt;

&lt;p&gt;正好我们的埋点记录了开始分享、结束分享的时间，通过计算时间，如果大部分用户的取消用时间都比较长，那么就可以放心的认为这个错误影响并不大。&lt;/p&gt;

&lt;p&gt;按照这个思路，我下载了一天当中两个小时的分享数据，然后写脚本计算了这些数据当中低于2秒的取消，占比在10%左右，考虑到手比较快取消掉的因素，又检查低于0.5秒的取消，占比不超过5%，那么可以判断出这个错误影响并不严重。&lt;/p&gt;

&lt;p&gt;也可以用SQL直接查询，这样可以针对一整天采样，效率更高。&lt;/p&gt;

&lt;p&gt;不过我们还遇到另外一个案例就不能这样简单的解决，这个问题是vivo 的Android 11手机上可能出现分享面板没有绘制出来，但是仍然保留了交互能力，对于这样的问题就比较复杂，可能可以通过检测被误点的按钮来排查，不过还没有尝试。&lt;/p&gt;

&lt;h3 id=&quot;主动探索未知的暗物质错误&quot;&gt;主动探索未知的“暗物质错误”&lt;/h3&gt;
&lt;p&gt;当用户反馈以后，我们就知道了某种“暗物质错误”的表现形式，按图索骥的去查出一个估计的错误数量级，能够评估错误严重程度，或者尝试与疑似用户联系取得更多的错误样本帮助解决问题，固然比收到反馈无可奈何好得多。但是有没有可能主动出击，在用户反馈以前就知道可能存在问题，通过增加日志、主动联系可能出问题用户方式解决问题呢？&lt;/p&gt;

&lt;p&gt;简单一点的办法是找出“不合理的埋点”。好比分享埋点，如果从启动到结束的时间特别短，短得低于常识，不管成功、失败还是取消，都是很可疑的现象，值得增加日志去排查。虽然埋点上报了，但是它不合理，那么这很大概率是一个“暗物质错误”在作妖。&lt;/p&gt;

&lt;p&gt;还有一个比较大胆的想法，我们的用户数据足够多，不知道能不能引入大数据分析，判断出来偏离主流用户的一些行为，如果偏离超过2个标准差一般就认为很异常了。类似前面的第二个案例，它没有简单的判断方法，但是因为用户看不到界面，却可以交互，就会发生乱点按钮的现象，如果可以通过一定方式判断“乱点按钮”，对我们已知的vivo Android 11用户群体重点排查，也许可以估计出来问题影响范围。我做客户端开发，对大数据分析是外行，希望专家们看一点是否有可能？&lt;/p&gt;

&lt;p&gt;不过坦率讲，这样做可能投入产出比不高。&lt;/p&gt;

&lt;h3 id=&quot;暗物质错误的总量估计&quot;&gt;“暗物质错误”的总量估计&lt;/h3&gt;
&lt;p&gt;科学家估计宇宙中的暗物质占比大约是26.8%，我们有没有可能估计一下这种“暗物质错误”的占比呢？&lt;/p&gt;

&lt;p&gt;具体的数值很难给出，但是好消息在于这类错误肯定概率很低，毕竟我们的发布流程是必须经过严格的测试才会上线，能够从我们的测试工程师手里逃过bug的，本身出现的概率就已经很低了，应该介于线上问题的数量和被发现的线上问题数量之间，所以是个很低的比例。&lt;/p&gt;

&lt;p&gt;也因此这类“暗物质错误”的捕获绝不是件容易的事情，如果容易，我们的测试工程师就不会放过它。数据工程师需要精心设计埋点得以互相验证，开发工程师也需要认真核对和维护埋点，一般刚刚添加的埋点是正确的，就怕代码重构或者修改附带修改埋点导致错误。做到这些，出现了此类问题的时候，我们就可能通过埋点之间的不合理现象捉出来这些“暗物质错误”。&lt;/p&gt;

</description>
        <pubDate>Sun, 25 Apr 2021 12:00:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2021/04/25/unknown.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2021/04/25/unknown.html</guid>
        
        <category>数据分析</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>互联网移动开发软件设计原则应用</title>
        <description>&lt;p&gt;SOLID软件设计原则大名鼎鼎，但是它在实际的软件开发过程中的应用很不充分，互联网移动开发又有它自己的特点。结合最近阅读《架构整洁之道》和我在互联网行业移动开发的经验，关于软件设计原则在移动端开发的应用，总结起来大概是这样的。
 &lt;!-- more --&gt;&lt;/p&gt;

&lt;p&gt;跳出面向对象的窠臼，设计原则就是设计原则，它不仅仅针对面向对象，不一定要用继承、封装、多态，也需要考虑组合等方式。
面向对象原则的SOLI都需要无条件的遵守，只要违背一定会遭受损失；D原则成本比较高，可以权衡成本。
SOLID原则不是细则，所以充分理解和梳理业务逻辑，是正确应用软件设计原则的前提，否则无从下手。
互联网移动端软件一般业务优先，往往代码质量稀烂，这时候要求设计师“脚踩烂泥，眼望星空”，在稀烂的代码当中，不要忘记软件设计原则。
设计原则不是银弹，但是它可以保证软件下限，遵守SOLID的设计，不会变的太烂。
自动测试成本偏高，基础建设成本高，可测试性差，边际成本可以低，可以作为最后一道防线，对于核心数据相关功能、SDK等基础模块，是非常必要的，可以起到说明文档的作用。
两个熟悉代码的人互相审核设计结构和代码，可以帮助发现设计问题，自己设计，问题很难自己发现，或者等到出现不良影响以后才能发现。
基础的SDK模块，要提供设计合理、结构清晰的示范代码，因为别人不会下功夫理解你的设计精妙之处，都是先抄过来用的。&lt;/p&gt;

</description>
        <pubDate>Thu, 12 Nov 2020 05:00:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/11/12/software-design.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/11/12/software-design.html</guid>
        
        <category>编程</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>Dokka命令行使用</title>
        <description>&lt;p&gt;Dokka是Kotlin官方推荐的文档生成工具，目前看来也是唯一的生成工具。在配置gradle方式的时候提示错误很有限，可以使用命令行形式配置。
 &lt;!-- more --&gt;&lt;/p&gt;

&lt;h3 id=&quot;官方文档&quot;&gt;官方文档&lt;/h3&gt;

&lt;p&gt;参考官方配置只是第一步，链接-&amp;gt;&lt;a href=&quot;https://kotlin.github.io/dokka/1.4.10.2/user_guide/cli/usage/&quot;&gt;官方命令行配置&lt;/a&gt;，这里没有说完整，所以还是运行不起来的。&lt;/p&gt;

&lt;h3 id=&quot;注意事项&quot;&gt;注意事项&lt;/h3&gt;

&lt;p&gt;一般这些问题会被忽略导致运行失败, 需要参考&lt;a href=&quot;https://discuss.kotlinlang.org/search?q=dokka&quot;&gt;社区讨论&lt;/a&gt;才能找到答案：&lt;/p&gt;

&lt;p&gt;1、pluginsClasspath参数和plugin处理: 从命令行运行，一定要把需要依赖的jar文件下载到本地，然后填写到pluginsClasspath参数，否则缺少相关依赖无法运行。&lt;/p&gt;

&lt;p&gt;2、sourceSet参数的设置非常特殊: 它下面还有其他参数，比如src，moduleName，需要写成-sourceSet “-src xxx -moduleName xxx”的形式&lt;/p&gt;

&lt;p&gt;这样就没问题了。下面是一个示范，src/main/java应该替换成自己实际的代码位置，./doc也应该替换成实际的用来放置帮助文档的目录；pluginsClasspath里面的各个文件也需要替换成实际的文件位置。&lt;/p&gt;

&lt;h3 id=&quot;正确运行的dokka命令行示范&quot;&gt;正确运行的dokka命令行示范&lt;/h3&gt;

&lt;p&gt;一个很长的命令行，据说也可以使用json格式配置，但是json格式配置没有探索出来：
java -jar ../dokkaplugin/dokka-cli-1.4.0-rc.jar -pluginsClasspath “../dokkaplugin/dokka-analysis-1.4.0-rc.jar;../dokkaplugin/dokka-base-1.4.0-rc.jar;../dokkaplugin/kotlin-analysis-compiler-1.4.0-rc.jar;../dokkaplugin/kotlin-analysis-intellij-1.4.0-rc.jar;../dokkaplugin/kotlinx-coroutines-core-1.4.0-M1.jar;../dokkaplugin/kotlinx-html-jvm-0.7.1-1.4.0-rc.jar” -sourceSet “-moduleName sampleModule -src src/main/java/” -outputDir ./doc&lt;/p&gt;

</description>
        <pubDate>Fri, 23 Oct 2020 10:00:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/10/23/dokka.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/10/23/dokka.html</guid>
        
        <category>Dokka</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>用数据特征排除数据波动假警告</title>
        <description>&lt;p&gt;在灰度包数据问题当中，经常出现数据波动超过了正常范围，但是无法查找到相关原因。第二天或者第三天，随着数据放量，数据波动恢复到了正常范围以内。根本上说这是数据量比较少导致的现象，这里探讨一种方法来判断这种警告是否可靠。[原文链接][原文链接]
 &lt;!-- more --&gt;&lt;/p&gt;

&lt;h3 id=&quot;问题&quot;&gt;问题&lt;/h3&gt;

&lt;p&gt;在灰度包数据问题当中，经常出现数据波动超过了正常范围，但是无法查找到相关原因。第二天或者第三天，随着数据放量，数据波动恢复到了正常范围以内。&lt;/p&gt;

&lt;p&gt;根本上说这是数据量比较少导致的现象，有时候也可能是灰度到的用户群体带有一定特殊性，导致数据失真。一个非常显著的例子是有时候个别用户会疯狂分享上千次，最多五千次，导致灰度数据暴涨。当然，这个例子其实也是数据样本不够大，只要足够大，这个暴涨的数据在巨大的基数上也微不足道。然而灰度本身就是希望现在小样本上做试验，确认效果以后再推广到全部数据，所以不能完全靠数据放量来进行判断。&lt;/p&gt;

&lt;p&gt;那么有没有可能根据当前灰度数据（样本）判断它是否可靠？对于可靠性低的样本反应出来的异常波动可以重新发灰度或者等一等数据放量，对于可靠性高的样本反应出来的异常波动就需要认真排查。&lt;/p&gt;

&lt;h3 id=&quot;解决思路&quot;&gt;解决思路&lt;/h3&gt;

&lt;p&gt;数据应该满足一定分布方式，如果样本数据严重偏离这个分布方式，就认为样本不可靠。比如在学校随机选择学生访问，如果全部是男生那么这个样本有问题的可能性很大，男女生比例应该接近1:1。&lt;/p&gt;

&lt;p&gt;在下面的描述当中，使用分享数据作为例子，显然灰度数据不存在好像男生女生一样1:1的简单比例，看起来也找不到正态分布的数据，那么：&lt;/p&gt;

&lt;h4 id=&quot;灰度数据应该满足什么分布&quot;&gt;灰度数据应该满足什么分布？&lt;/h4&gt;
&lt;p&gt;观察apm数据的图表，基本每天的都是如下图形：
&lt;img src=&quot;/assets/img/2020_10_22_data_pic1.png&quot; alt=&quot;PNG&quot; /&gt;
这是随着时间变化的分享次数数量，当出现bug的时候，可能分享平台比例有变化，可能某些分享数量会降低，但是同一个逻辑的分享数据，很难改变用户在什么时候分享。所以在这里选择 时间-次数 的关系图形作为标准分布，期望灰度数据也可以满足这个分布图形。&lt;/p&gt;

&lt;p&gt;在这里没有做条件限制，实际使用的时候是具体数据有问题，比如出现QQ数据偏差，那么可以对分布数据和样本数据都限制为QQ数据然后进行比较，这样更加精确，确实QQ的分布情况和大盘的分布情况不太一样。但是带来的问题是数据量缩小可能导致判断不准确。根据经验，21点的数据不应该少于200，最好能多于1000个，这样比较可靠。&lt;/p&gt;

&lt;h4 id=&quot;怎么描述这个分布&quot;&gt;怎么描述这个分布？&lt;/h4&gt;
&lt;p&gt;述图形是很不规律的，无法用简单的函数进行描述。可以使用最小二乘法逼近一个函数出来，不过在这里我们简单处理，&lt;/p&gt;

&lt;p&gt;1、对全部分享的每天的数据，每小时求和，得到S1…S24&lt;/p&gt;

&lt;p&gt;2、从中取得最大值Smax&lt;/p&gt;

&lt;p&gt;3、用Ri = Si/Smax，得到R1…R24&lt;/p&gt;

&lt;p&gt;4、理想的灰度数据按照相同的方式处理以后得到的R1..R24应该和上述数据一致&lt;/p&gt;

&lt;p&gt;实际计算21点最多为1，其他比例从0.07 - 1之间。因为灰度数据一般在周一和周二，所以选择了最近四周的周一周二一共八天，计算比例以后求平均值如下：&lt;/p&gt;

&lt;p&gt;[0:0.3401366175169934, 1:0.1861001156380886, 2:0.10706734635811115, 3:0.07250220823703654, 4:0.07431978413956412, 5:0.136710199297124, 6:0.2697665388808243, 7:0.358382774809046, 8:0.42053193670871464, 9:0.4944465748090594, 10:0.5622043101012557, 11:0.6023667672832733, 12:0.6880661656315542, 13:0.6697479553558153, 14:0.5965930339537727, 15:0.5874068054395511, 16:0.5964191765794082, 17:0.6306011447003349, 18:0.6831739276417802, 19:0.7587048965272791, 20:0.9088487380987144, 21:1.0, 22:0.8840385602760035, 23:0.5996796741915665]&lt;/p&gt;

&lt;p&gt;这里虽然使用的是全部数据，但是也不是一个理想的比例，所以不会完全一样。&lt;/p&gt;

&lt;h4 id=&quot;怎么衡量灰度数据与这个分布的偏差&quot;&gt;怎么衡量灰度数据与这个分布的偏差？&lt;/h4&gt;

&lt;p&gt;平时的灰度数据不可能是理想的灰度数据，我们参考标准差计算公式来衡量灰度数据和这个分布的偏差。&lt;/p&gt;

&lt;p&gt;设分布数据是R1..R24，灰度数据是H1..H24，那么有：&lt;/p&gt;

&lt;p&gt;delta = Sqrt(Sum((Ri - Hi)^2) / N) N = 24&lt;/p&gt;

&lt;p&gt;理想情况下delta 应该为0，选择全部数据的时候计算出来的delta在0.01左右，说明R1..R24还是很可靠的。选择200万的灰度版本计算出来的delta在0.05 - 0.12之间。&lt;/p&gt;

&lt;p&gt;那么，如果灰度数据的偏差超过0.1 我们可以认为是比较大的。&lt;/p&gt;

&lt;h4 id=&quot;sql语句和脚本文件&quot;&gt;sql语句和脚本文件&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;colum1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200427&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200426&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200425&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200424&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appver&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;1.1.20.302&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;QQ&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;START&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;这里是对灰度版本1.1.20.302的QQ平台获取数据，设定在4.24 - 4.27四天。如果是获取全部数据，应该修改天数并且删除版本号限制。&lt;/p&gt;

&lt;p&gt;可能也可以通过查询数据然后使用Excel的数据透视图的方式来计算，不用groovy脚本这样写程序来计算。&lt;/p&gt;

&lt;h3 id=&quot;使用时间序列-百分比变化图形&quot;&gt;使用时间序列-百分比变化图形&lt;/h3&gt;

&lt;p&gt;我的同事wangyudong提出可以直接使用时间序列-百分比变化图形，虽然目前这个图形的原理是什么还不清楚，但是在最近两次经验判断比较准&lt;/p&gt;

&lt;p&gt;使用两个灰度版本+最近的大盘版本，三条曲线应该尽量接近。可能使用一小时作为时间单位会比较合适。&lt;/p&gt;

&lt;h3 id=&quot;验证和举例&quot;&gt;验证和举例&lt;/h3&gt;

&lt;p&gt;在偏差很大的情况下，可以直接认为数据不可靠，如果能定位不可靠原因，那么该灰度数据还是可以有结论的；如果不能定位不可靠原因，要么等待积累更多数据偏差变小，要么重新发灰度。偏差比较小的情况下，结合数据总量和偏差量的变化趋势，也能帮助说明一定问题。&lt;/p&gt;

&lt;h4 id=&quot;分平台私信分享pv成功次数-涨幅异常&quot;&gt;分平台：私信分享pv，成功次数 涨幅异常&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200410&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appver&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;1.3.10.358&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;START&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;分平台pv成功次数uv-涨幅异常&quot;&gt;分平台：pv，成功次数，uv 涨幅异常&lt;/h4&gt;

&lt;p&gt;QQ数据异常，不存在明显头部数据现象，但是随着时间逐步正常了；私信数据确实存在异常。&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200427&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200426&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200425&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appver&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;1.3.30.503&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;START&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;QQ&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;QQ数据在三天时间里面分别是0.081 → 0.079 → 0.072 逐步变小，变化不显著，但是可以注意到，这三天数据也在变少，一般数据变少差异变大，这里出现相反的情况，说明数据质量提高的幅度应该超过了0.081 到0.072。&lt;/p&gt;

&lt;h4 id=&quot;分平台成功次数人均分享次数-涨幅异常&quot;&gt;分平台：成功次数，人均分享次数 涨幅异常&lt;/h4&gt;

&lt;p&gt;这个版本入视频详情页的分享中台SDK出现问题，暂时关闭开关，之后多次使用灰度版本验证，很久才确认没有问题发版。如果用这个方式来检测灰度样本偏差成都，首先使用短视频数据作为基准数据：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200727&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200728&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200720&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200721&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200714&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;20200713&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200707&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200706&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;START&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;PD&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;然后以1.4.30.140版本为例：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fcount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;table1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200530&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200601&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;20200602&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;appver&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;1.4.30.140&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;START&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;PD&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_date&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p_hourmin&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;limit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;这样计算出来的偏差是 0.057， 0.065， 0.070 是比较小的，那么实际不用那么晚才确认数据可靠。&lt;/p&gt;

&lt;h4 id=&quot;qq分享---分平台成功次数人均分享次数pv-波动异常&quot;&gt;QQ分享 - 分平台：成功次数，人均分享次数，pv 波动异常&lt;/h4&gt;

&lt;p&gt;这个版本判断是正常波动，用该工具计算结果也是正常波动，使用时间序列-百分比变化也是正常波动，出现异常的那一天偏离较大&lt;/p&gt;

&lt;h4 id=&quot;分享-直播分享qq和微信分享指标异常pv成功次数uv-波动异常&quot;&gt;分享-直播分享，qq和微信分享指标异常：pv，成功次数，uv 波动异常&lt;/h4&gt;

&lt;p&gt;这个版本判断暂时认为是正常波动，因为8号、10号数据正常，但是9号数据异常。可是使用该工具计算，9号可靠度更高，8号其次，10号最不可靠。&lt;/p&gt;

&lt;p&gt;使用时间序列-百分比变化的结论是8号、10号数据可靠，9号数据不可靠：&lt;/p&gt;

&lt;p&gt;8号数据：
&lt;img src=&quot;/assets/img/2020_10_22_data_pic8.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;9号数据：
&lt;img src=&quot;/assets/img/2020_10_22_data_pic9.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;10号数据：
&lt;img src=&quot;/assets/img/2020_10_22_data_pic10.png&quot; alt=&quot;PNG&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;目前的不足和改进方向&quot;&gt;目前的不足和改进方向&lt;/h3&gt;
&lt;p&gt;首先，可以发掘更多的指标，这里只用了时间-次数的关系，如果能增加其他指标共同判断，会更有说服力；&lt;/p&gt;

&lt;p&gt;第二，使用的数学工具有待改进，现在只用了平均数和标准差计算，而且衡量灰度数据和分布数据的计算其实还不是标准差，如果使用更好的数学工具可能进一步增加数据说服力；方法上也可以引入其他方式，比如尝试其他抽样方式例如Bootstrap等&lt;/p&gt;

&lt;p&gt;第三，再多用现有灰度计算，置信范围可以比&amp;lt;0.1更精确一些&lt;/p&gt;

&lt;p&gt;第四，除了“标准差”计算以外，可以引入数据数量结合判断，小数据量而且标准差小的样本应该是质量更高的样本&lt;/p&gt;

</description>
        <pubDate>Thu, 22 Oct 2020 12:00:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/10/22/data.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/10/22/data.html</guid>
        
        <category>数据分析</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>Flutter的淘宝第三方登录</title>
        <description>&lt;p&gt;受人之托研究一个Flutter的问题，淘宝登陆如何在Flutter上完成，淘宝登陆本身库很久没有更新，文档也写的不太好，Flutter偏偏是个新事物，所以出了不少问题。淘宝第三方登录属于阿里百川开发SDK的内容，没有提供Flutter包，所以只能按照Flutter的不同平台开发来处理，这里主要是Android的登录问题。
 &lt;!-- more --&gt;&lt;/p&gt;

&lt;h3 id=&quot;参考正确的文档&quot;&gt;参考正确的文档&lt;/h3&gt;

&lt;p&gt;这是正确的淘宝登陆应该参考的文档：&lt;a href=&quot;https://baichuan.taobao.com/docs/doc.htm?spm=a3c0d.7629140.0.0.H5PomY&amp;amp;treeId=129&amp;amp;articleId=105647&amp;amp;docType=1&quot;&gt;百川SDK文档1&lt;/a&gt; 或者参考 &lt;a href=&quot;https://baichuan.taobao.com/docs/doc.htm?spm=a3c0d.7629140.0.0.3690be48BbYbXu&amp;amp;treeId=129&amp;amp;articleId=118400&amp;amp;docType=1&quot;&gt;百川SDK文档2&lt;/a&gt; 前者是我在别人的文档里面看到的，但是没有在百川文档里面找到入口，后者是从“百川电商SDK”的文档入口找到的。&lt;/p&gt;

&lt;p&gt;我们一般注册百川以后，在控制台看到“云账号”等内容就会进去较劲，其实它不是我们要的淘宝第三方登录。真正的第三方登录放在了“百川电商”里面，这个命名方式深得程序员免于被裁员之妙：故意用一个错误的命名，这样你就很难被取代了。&lt;/p&gt;

&lt;h3 id=&quot;基本步骤&quot;&gt;基本步骤&lt;/h3&gt;

&lt;p&gt;注册应用、上传SDK和获取安全图片，网上很多文档还推荐使用v4版本安全图片，但是现在只能用v5版本了。&lt;/p&gt;

&lt;p&gt;然后进入“套件申请”、“API申请”，应该需要的是“淘宝客基础页面包”和“系统工具”、“百川基础能力”、“百川网关基础权限包”这四个，如果没有申请会在初始化的时候报错。&lt;/p&gt;

&lt;h3 id=&quot;解决androidx和support包的冲突&quot;&gt;解决Androidx和Support包的冲突&lt;/h3&gt;

&lt;p&gt;现在Android开发已经用Androidx取代了support包来处理以前版本的兼容问题，但是百川SDK还不支持，如果你的Flutter应用在配置了淘宝第三方登录以后编译失败，提示找不到support包，说明是Flutter里面引用的第三方库或者你自己引用了androidx，这个问题没有别的办法，只能取消对Androidx的引用来解决。如果是自己引用的，就想办法换掉；如果是第三方Flutter库引用的，要么使用这些库支持support包的版本，要么换其他库或者自己写。&lt;/p&gt;

&lt;p&gt;另外就是Flutter如果升级到当前版本1.12以上，很可能Flutter本身就已经引用了androidx，这时候只能用以前老版本的Flutter，我用的是1.7.8版本可以，没有测试具体哪个版本开始不能用的。&lt;/p&gt;

&lt;p&gt;或者不要做淘宝第三方登录。&lt;/p&gt;

&lt;p&gt;幻想让Androidx和Support包和平共存是不太可能的。&lt;/p&gt;

&lt;h3 id=&quot;如果还有问题&quot;&gt;如果还有问题&lt;/h3&gt;

&lt;p&gt;可以去百川社区提问或者搜索：&lt;a href=&quot;https://baichuan.bbs.taobao.com/list.html&quot;&gt;百川社区&lt;/a&gt;一般还是有参考信息的，但是找到直接能解决问题的还不太容易。&lt;/p&gt;

&lt;p&gt;不过接入一个第三方登录，终归不会太难的。&lt;/p&gt;

</description>
        <pubDate>Wed, 01 Jan 2020 10:45:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/01/01/flutter-taobaologin.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2020/01/01/flutter-taobaologin.html</guid>
        
        <category>Flutter</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>UiAutomator试用</title>
        <description>&lt;p&gt;测试驱动开发是个好东西，但是它需要以自动测试为前提，自动测试对于移动应用开发似乎太难了。不过UiAutomator其实是可以解决这个问题的，不过有些坑实在太难为人，其他一些问题，我以为还是有办法解决的，只要成本划算是可以做的。
 &lt;!-- more --&gt;&lt;/p&gt;

&lt;h3 id=&quot;界面改变导致测试用例失败&quot;&gt;界面改变导致测试用例失败&lt;/h3&gt;

&lt;p&gt;这个问题很常见，也是针对界面自动测试最为令人诟病的一点。但是我们只要考虑一下，界面改变其实是需求发生改变导致的，或者开发提出技术需求进行重构导致，那么如果我们要求每次提交都应该以自动测试用例通过为前提，付出一定维护代价是可以避免的。&lt;/p&gt;

&lt;p&gt;这个代价也不会太大，只不过是修改几个界面ID而已，几百行代码都改了，几个界面ID改改能有多大带代价？用UiAutomator不过几分钟的工夫而已。&lt;/p&gt;

&lt;h3 id=&quot;自动测试报假警告太多&quot;&gt;自动测试报假警告太多&lt;/h3&gt;

&lt;p&gt;如果这样的问题太多，确实是自动测试用例的致命问题，报告错误了查半天发现不是bug，而是测试代码写的不好导致，是十分伤害信心的。&lt;/p&gt;

&lt;p&gt;这个问题只要把测试代码写好就行了，确实测试在这方面无能为力。可以通过要求开发来写测试代码来解决：1、如果做TDD，那么开发有义务写；2、开发写代码能力总比测试强（当然不排除有些开发强的有限）；3、开发是从白盒角度，可以看到逻辑的情况下写的，具备避免AB测试等逻辑问题的能力。&lt;/p&gt;

&lt;h3 id=&quot;测试用例不稳定&quot;&gt;测试用例不稳定&lt;/h3&gt;

&lt;p&gt;有时候能成功，有时候不能成功，甚至半路停下来了。这种请更换手机，华为手机有这个毛病，小米、夏普手机运行非常稳定，没有任何问题，UiAutomator的可靠性是足以保证测试用例正常执行的。&lt;/p&gt;

&lt;h3 id=&quot;写测试用例花太多时间&quot;&gt;写测试用例花太多时间&lt;/h3&gt;

&lt;p&gt;不多，一般做好几天的需求，测试用例连写带调试最多一个小时。&lt;/p&gt;

</description>
        <pubDate>Sun, 03 Nov 2019 11:54:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2019/11/03/uiautomator1.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2019/11/03/uiautomator1.html</guid>
        
        <category>TDD</category>
        
        <category>UiAutomator</category>
        
        
        <category>编程</category>
        
      </item>
    
      <item>
        <title>Java to Kotlin</title>
        <description>&lt;p&gt;Kotlin确实是全面兼容Java的，但是也有一些不一样的地方，如果不注意很容易造成误解。&lt;/p&gt;

&lt;h3 id=&quot;classjava-vs-javaclass&quot;&gt;class.java vs javaClass&lt;/h3&gt;
&lt;p&gt;&lt;!-- more --&gt;&lt;/p&gt;

&lt;p&gt;在绑定服务的时候，开始是这样写的：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bindIntent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@BaseActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;DataService:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;javaClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;javaClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;无论如何也不能成功，后来发现应该这样写：&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bindIntent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Intent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@BaseActivity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;DataService:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;存在两个问题：&lt;/p&gt;

&lt;p&gt;1、为什么第一种做法写一个javaClass提示错误，这样写两个就编译通过了？&lt;/p&gt;

&lt;p&gt;2、为什么第二种可以用，第一种不能用？&lt;/p&gt;

&lt;h3 id=&quot;inner-class&quot;&gt;inner class&lt;/h3&gt;

&lt;p&gt;在Java当中，默认就是内部类，如果要声明静态类需要使用static关键字。但是Kotlin当中修改为默认就是静态类，需要声明内部类才使用inner关键字。&lt;/p&gt;

&lt;p&gt;当程序员误以为自己声明了一个内部类，试图引用外部类的this对象，写出this@的时候，并没有任何提示，对Java程序员是一个不大不小的困扰。这样的改动并非只有这一处，比如默认的类都是不可继承的，但是当程序员试图继承一个类的时候，Idea会提示把类改为Open模式，避免了这种修改可能导致的问题。&lt;/p&gt;

</description>
        <pubDate>Tue, 06 Nov 2018 06:54:00 +0000</pubDate>
        <link>http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2018/11/06/java2kotlin.html</link>
        <guid isPermaLink="true">http://blog.chenchi.cc//%E7%BC%96%E7%A8%8B/2018/11/06/java2kotlin.html</guid>
        
        <category>Kotlin</category>
        
        
        <category>编程</category>
        
      </item>
    
  </channel>
</rss>
