就我那點垃圾程式碼竟然也有人要抄?

neverwoods發表於2019-05-10

說不改了,可逃不過真香,這次是死也不改版

就我那點垃圾程式碼竟然也有人要抄?

把他這段話改一改,差不多就是我的心聲吧。

『讓別人知道怎麼維護自己的權利做錯什麼了嗎?我辛辛苦苦維護了好一陣子的專案,開開心心發到網上給大家用,然後你幹啥了,躲在我不知道的地方 copy 專案,踩在我身上上架外掛,到底誰做錯了?目前該揭露的都揭露了,該罵的也罵了,互不相欠!那都是便宜你了』

更新 最後一次

我發現這人有個很奇怪的邏輯,一直在強調他做的比我好,就好像只要做得比我好了,他就什麼錯都沒有了。從一開始在 issue 中回覆我,到掘金裡的新專案預告,到新專案半成品出爐了之後的話,總是明裡暗裡說他做得怎麼怎麼好。

正常的開發者,只要腦袋正常,恐怕都幹不出這種 copy 別人程式碼,然後隨便改點地方就作為自己原創釋出到 github,甚至是 idea 官網這種事情,更不要提自己用小號給自己評分五星,還問我怎麼不想想為什麼我下載量比較少。難道覺得這件事情乾得很過分,我還反而是沒風度了?

我再重申一下我的觀點,開源專案你下載下來自己改沒什麼,但是你作為原創再發布,甚至還拿去上架,這哪個原創作者看到不心塞?想要對開源事業做貢獻,正確的做法是 fork 之後再改,改完還可以提 pull request,這才是真正在 github 上做貢獻的正確姿勢,不然你改一個,他改一個,各自維護各自的,沒有一個輪子能做起來的。

從一開始,我就沒指望給他的 issue 能起到什麼作用,事實上我也沒判斷錯。他面對被我抓了個現成這件事情,毫無愧疚感,也壓根不解釋為什麼刪我註釋,在我不知情的情況下自行釋出上架。他唯一提供的補償方式就是新增一個遲來的感謝連結,然後很委屈的表示『有時間重構就是了』。

對於這麼一個人,我還能說什麼呢?多說幾句我發現我脾氣也上來了,也快管不住自己嘴了。他好歹說了一句人話,那就是這事再鬧下去大家都浪費時間。可我想說的是,如果抄襲的人都像他這麼明目張膽死不認錯,被抄襲的人都忍氣吞聲,這才是 TMD 有病吧?

好了,發洩夠了也就不會再更新了。

最後,希望大家釋出原創的時候記得加 LICENSE,這是對自己僅有的一點保障;也希望大家在用別人的程式碼的時候,帶有一絲尊重。

更新 2019-05-13 23:10

那位老哥上傳了自己獨立完成的程式碼,我去看了下程式碼,做了一些花裡胡哨的封裝。而且在我做了測試之後感覺他的專案完全還不是能用的程度,bug 實在太多。當然,這麼短的時間想做出完成度高的東西,確實太難了,只是我真的好驚奇,他發東西出來懟我之前自己都不測試檢查一下的嗎?boolean 對映成了 num 型別,null 解析不了,空陣列直接無響應,排版有各種問題,資料複雜了之後甚至基本的輸出文字都是錯的,諸如此類的問題不要太多。我也是人太好了,免費幫他當了一把測試人員。以這位老哥吊炸天的編碼能力都寫出了這麼多的 bug,我想我甚至都低估了我當初做這個專案時付出的精力。只是這種努力,恐怕在他看來還是不值一提。

當然,道歉是不可能道歉的,目前來看一切似乎都是我的錯。

就我那點垃圾程式碼竟然也有人要抄?

更新 2019-05-13 11:30

感謝 @呂中宜,@FeelsChaotic 兩位提醒我用文字對比工具。

下面附上我提交的兩個核心檔案的對比結果,額外說明一下,我的程式碼使用的是1月份的,另外一個人的程式碼使用的是3月份 github 第一次提交的。

ClassGenerator

就我那點垃圾程式碼竟然也有人要抄?

對比結果:

106 個相同行

11 個不重要的左邊獨有行

12 個不重要的右邊獨有行

4 個不重要的差異行

10 個重要的左邊獨有行

37 個重要的右邊獨有行

19 個重要差異行

25 個差異部分

Param

就我那點垃圾程式碼竟然也有人要抄?

對比結果:

108 個相同行

2 個重要右邊獨有行

2 個重要差異行

4 個差異部分

更新 2019-05-11 18:20

那位老哥在 github issue 上回復我了

就我那點垃圾程式碼竟然也有人要抄?

對此我想說的是:

1.有問題可以提 issue, 也可以 fork,心情好的話改了之後還可以提 PR,心情不好就自己用自己的修改版。我個人不能認同因為我的程式碼不能被直接使用在你個人的專案中,就直接 copy 我的程式碼,刪掉我的註釋,改幾個命名,然後作為你的原創專案釋出到網上去。

2."於是我本地做了大量改動",說實話,或許這位哥們還覺得自己很委屈,覺得自己改了很多程式碼,但是我覺得比起我從零做起,這點真的不值一提。無論是判斷一下空值,還是型別有問題,放我手上都是分分鐘解決的問題,犯得著你如此辛苦做這麼大奉獻嗎?

3."此時的程式碼基本只有json字串與json物件轉換工具類是使用你的庫裡的",如果你覺得你只用了我這點程式碼,或者你覺得我這點程式碼不值一提,為什麼不自己重寫,反而讓我一看到處都是我的程式碼?

4."硬要說網上的程式碼都是不允許別人使用的那我也沒辦法啊",看到這裡我真是哭笑不得,這還都是我的錯咯?到底是誰該委屈啊?我之前連 LICENSE 都沒加一個,難道我會不讓別人用?刪我註釋、自己釋出這個核心問題就被這麼給忽略掉了?

5."以後有時間我把裡面的那個工具類重構下就是了",鬧了半天,copy 了整個專案,隨便改了幾個地方,現在還覺得就用了下工具類?

以下原文

小弟不才,去年學習 flutter 的過程中,搞過幾個小玩具,其中就有一個將 json 資料轉換成 dart class 的 idea 外掛。

gayhub 傳送門

不想點的朋友可以看一下 gif

就我那點垃圾程式碼竟然也有人要抄?

半年多來,隨著 flutter 的火熱,github 上也有了那麼幾十個 star。後來我也釋出到了全家桶外掛官網。陸陸續續也有幾千個的下載量了。對於我這個小透明來說,還是挺開心的,不管寫得有多爛,總歸是有人在用,總歸是為 flutter 的生態貢獻了自己的一點綿薄之力。

然而就在今天,當我想去看看那些下載量更高的同類專案的時候,很不湊巧的就讓我發現了這麼一個作品:

先上一張他的 gif 吧

就我那點垃圾程式碼竟然也有人要抄?

敏感的我,一下子就感覺到了有點不對勁,於是我點開了他的原始碼開始檢視,有如下收穫:

我先看了下原始碼目錄

我的

就我那點垃圾程式碼竟然也有人要抄?

他的

就我那點垃圾程式碼竟然也有人要抄?

1.ClassGenerator 類名一樣的

2.NameValuePair/NamePair 極其相似,而且講道理,NamePair 這個命名很不自然

3.Param 類名一樣

4.Util/Utils 基本沒差別

於是我開始對比程式碼細節

沒耐心的朋友可以不用看程式碼,我以我的人格擔保,最後的結論是沒錯的

ClassGenerator

我的

class ClassGenerator(private val generateComments: Boolean, private val ignoreEmptyOrNull: Boolean) {
    val classes = mutableMapOf<String, List<Param>>()

    fun generate(name: String, string: String): String {
        return try {
            val parseResult = JsonParser().parse(string)

            val json: JsonObject? = if (parseResult.isJsonObject) {
                parseResult.asJsonObject
            } else if (parseResult.isJsonArray) {
                parseResult.asJsonArray[0].asJsonObject
            } else null

            val fields = Param.json2Params(json)
            "class $name {\n${printClassWithParams(fields, 2, name)}\n}\n${buildClasses()}"
        } catch (jsonParseException: JsonParseException) {
            jsonParseException.printStackTrace()
            "error: not supported json"
        } catch (illegalStateException: IllegalStateException) {
            illegalStateException.printStackTrace()

            if (illegalStateException.message?.startsWith("Not a JSON Object") == true) {
                "error: not supported json"
            } else {
                "error: unknown"
            }
        }
    }

    private fun printClassWithParams(params: List<Param>, space: Int, className: String): String {
        val commentSb = StringBuilder()
        val sb = StringBuilder()

        val tempClasses = HashMap<String, List<Param>>() // 統計子類

        var spaceStr = ""
        repeat(space) { spaceStr += " " }

        val commentPrefix = "$spaceStr *"
        fun List<Param>.insertComment(): List<Param>  {
            return if (generateComments) this.map {
                commentSb.append(commentPrefix).append(" ${it.comment}\n")
                it
            } else this
        }

        /* 基本型別引數宣告與統計 **/
        val orderedList = params
                .filter { it.key == "String" || it.key == "int" || it.key == "double" || it.key == "bool" || (!ignoreEmptyOrNull && it.key == "dynamic") }
                .sortedBy { it.key }
                .insertComment()
                .map {
                    sb.append("$spaceStr${it.key} ${it.value};\n")
                    it.value
                }

        /* 物件型別引數宣告與統計 **/
        val objectList = params
                .filter { it.key == "object" }
                .sortedBy { it.value }
                .insertComment()
                .map {
                    val clazzName = Util.toUpperCaseFirstOne(it.value + "Bean")
                    classes[clazzName] =  it.clazz
                    tempClasses[clazzName] = it.clazz
                    sb.append(spaceStr).append(clazzName).append(" ").append(it.value).append(";").append("\n")
                    NameValuePair(clazzName, it.value)
                }

        /* 基本型別 list 引數宣告與統計 **/
        val listBaseList = params
                .filter { it.key.startsWith("List<") }
                .filterNot { it.key.contains("null") }
                .sortedBy { it.value }
                .insertComment()
                .map {
                    sb.append(spaceStr).append(it.key).append(" ").append(it.value).append(";").append("\n")
                    NameValuePair(it.key, it.value)
                }

        /* 物件型別 list 引數宣告與統計 **/
        val listList = params
                .filter { "list" == it.key }
                .filter { it.clazz != null }
                .sortedBy { it.value }
                .insertComment()
                .map {
                    val clazzName = Util.toUpperCaseFirstOne(it.value + "ListBean")
                    classes[clazzName] = it.clazz
                    tempClasses[clazzName] = it.clazz
                    sb.append(spaceStr).append("List<").append(clazzName).append(">").append(" ").append(it.value).append(";").append("\n")
                    NameValuePair(clazzName, it.value)
                }

        /* dynamic list **/
        var dynamiclist: List<NameValuePair>? = null
        if (!ignoreEmptyOrNull) {
            dynamiclist = params
                    .filter { it.key == "dynamicList" }
                    .sortedBy { it.value }
                    .insertComment()
                    .map {
                        sb.append(spaceStr).append("List<dynamic>").append(" ").append(it.value).append(";").append("\n")
                        NameValuePair(it.key, it.value)
                    }
        }

        val tempSpaceStr = "$spaceStr  "

        /* map.value 轉換為物件的靜態函式 start **/
        val fieldName = Util.toLowerCaseFirstOne(className)
        sb.append("\n").append(spaceStr)
                .append("static ").append(className).append(" fromMap").append("(Map<String, dynamic> map) {")
                .append("\n").append(tempSpaceStr)
                .append(className).append(" ").append(fieldName).append(" = new ").append(className).append("();")

        orderedList.forEach {
            sb.append("\n").append(tempSpaceStr).append(fieldName).append(".").append(it).append(" = ").append("map['").append(it).append("'];")
        }

        dynamiclist?.forEach {
            sb.append("\n").append(tempSpaceStr).append(fieldName).append(".").append(it.value).append(" = ").append("map['").append(it.value).append("'];");
        }

        objectList.forEach {
            sb.append("\n").append(tempSpaceStr).append(fieldName).append(".").append(it.value).append(" = ").append(it.name).append(".fromMap(map['").append(it.value).append("']);")
        }

        listList.forEach {
            sb.append("\n").append(tempSpaceStr).append(fieldName).append(".").append(it.value).append(" = ").append(it.name).append(".fromMapList(map['").append(it.value).append("']);")
        }

        /* map.value 轉換為基礎型別 list start **/
        if (listBaseList.isNotEmpty())
            sb.append("\n")
        for ((count, pair) in listBaseList.withIndex()) {
            sb.append("\n").append(tempSpaceStr).append("List<dynamic> dynamicList").append(count).append(" = map['").append(pair.value).append("'] ?? [];")
            sb.append("\n").append(tempSpaceStr).append(fieldName).append(".").append(pair.value).append(" = new List();")

            var function = "o.toString()"
            when (pair.name) {
                "List<int>" -> function = "int.parse(o.toString())"
                "List<double>" -> function = "double.parse(o.toString())"
                "List<bool>" -> function = "o.toString() == 'true'"
            }

            sb.append("\n").append(tempSpaceStr).append(fieldName).append(".").append(pair.value).append(".addAll(dynamicList").append(count).append(".map((o) => ").append(function).append("));")
            sb.append("\n")
        }
        /* map.value 轉換為基礎型別 list end **/

        sb.append("\n").append(tempSpaceStr).append("return ").append(fieldName).append(";\n")
        sb.append(spaceStr).append("}\n")
        /* map.value 轉換為物件的靜態函式 end **/

        /* map.value 轉換為 list 的靜態函式 start **/
        sb.append("\n").append(spaceStr)
                .append("static ").append("List<").append(className).append(">").append(" fromMapList").append("(dynamic mapList) {")
                .append("\n").append(tempSpaceStr).append("if (mapList == null) return [];")
                .append("\n").append(tempSpaceStr).append("List<").append(className).append("> list = new List(mapList.length);")
                .append("\n").append(tempSpaceStr).append("for (int i = 0; i < mapList.length; i++) {")
                .append("\n").append(tempSpaceStr).append("  ").append("list[i] = fromMap(mapList[i]);")
                .append("\n").append(tempSpaceStr).append("}")
                .append("\n").append(tempSpaceStr).append("return list;")
                .append("\n").append(spaceStr).append("}")
                .append("\n")
        /* map.value 轉換為 list 的靜態函式 end **/

        // 遍歷類中類(主要目的是新增進 classes 統計,而不 append)
        // 還是由於 dart 不支援內部類導致的
        tempClasses.forEach { key, value ->
            printClassWithParams(value, space + 2, key)
        }

        val commentString = if (commentSb.toString().isBlank() || commentSb.isEmpty()) {
            ""
        } else {
            "\n$spaceStr/**\n$commentSb$spaceStr */\n\n"
        }

        return "$commentString$sb"
    }

    private fun buildClasses(): String {
        val sb = StringBuilder()

        // 開始定義類
        classes.forEach { key, value ->
            sb.append("\n")
                    .append("class").append(" ").append(key).append(" ").append("{").append("\n")
                    .append(printClassWithParams(value, 2, key))
                    .append("}").append("\n")
        }

        return sb.toString()
    }
}
複製程式碼

他的

class ClassGenerator {
    val classes = mutableMapOf<String, List<Param>>()

    fun generate(name: String, jsonText: String): String {
        return try {

            val fields = Param.json2Params(JsonParser().parse(jsonText).asJsonObject)
            "class $name {\n${printClassWithParams(fields, 2, name)}\n}\n${buildClasses()}"
        } catch (jsonParseException: JsonParseException) {
            jsonParseException.printStackTrace()
            "error: not supported json"
        } catch (illegalStateException: IllegalStateException) {
            illegalStateException.printStackTrace()

            if (illegalStateException.message?.startsWith("Not a JSON Object") == true) {
                "error: not supported json"
            } else {
                "error: unknown"
            }
        }
    }

    fun generate(name: String, fields: JsonObject): String {
        return try {
            "class $name {\n${printClassWithParams(Param.json2Params(fields), 2, name)}\n}\n${buildClasses()}"
        } catch (jsonParseException: JsonParseException) {
            jsonParseException.printStackTrace()
            "error: not supported json"
        } catch (illegalStateException: IllegalStateException) {
            illegalStateException.printStackTrace()

            if (illegalStateException.message?.startsWith("Not a JSON Object") == true) {
                "error: not supported json"
            } else {
                "error: unknown"
            }
        }
    }

    private fun printClassWithParams(params: List<Param>, space: Int, className: String): String {
        val sb = StringBuilder()

        val tempClasses = HashMap<String, List<Param>>()

        var spaceStr = ""
        repeat(space) { spaceStr += " " }

        val orderedList = params
                .filter { it.key == "String" || it.key == "int" || it.key == "double" || it.key == "bool" || it.key == "num" }
                .sortedBy { it.key }
                .map {
                    sb.append("$spaceStr${it.key} ${it.camelWord};\n")
                    NameValuePair(it.value, it.camelWord)
                }

        val objectList = params
                .filter { it.key == "object" }
                .sortedBy { it.value }
                .map {
                    val clazzName = Utils.toUpperCaseFirstOne(it.value + "Bean")
                    classes[clazzName] = it.clazz
                    tempClasses[clazzName] = it.clazz
                    sb.append(spaceStr).append(clazzName).append(" ").append(it.camelWord).append(";").append("\n")
                    NamePair(it.camelWord, clazzName, it.value)
                }

        val listBaseList = params
                .filter { it.key.startsWith("List<") }
                .sortedBy { it.value }
                .map {
                    sb.append(spaceStr).append(it.key).append(" ").append(it.camelWord).append(";").append("\n")
                    NamePair(it.camelWord, it.key, it.value)
                }

        val listList = params
                .filter { "list" == it.key }
                .sortedBy { it.value }
                .map {
                    val clazzName = Utils.toUpperCaseFirstOne(it.value + "ListBean")
                    classes[clazzName] = it.clazz
                    tempClasses[clazzName] = it.clazz
                    sb.append(spaceStr).append("List<").append(clazzName).append(">").append(" ").append(it.camelWord).append(";").append("\n")
                    NamePair(it.camelWord, clazzName, it.value)
                }

        val tempSpaceStr = "$spaceStr  "


        /**
         * 構造
         */
        sb.append("\n").append(spaceStr)
                .append(className).append("({")

        orderedList.forEach {
            sb.append("this").append(".").append(it.value)
            sb.append(", ")
        }

        objectList.forEach {
            sb.append("this").append(".").append(it.camelKey)
            sb.append(", ")
        }

        listList.forEach {
            sb.append("this").append(".").append(it.camelKey)
            sb.append(", ")
        }

        listBaseList.forEach {
            sb.append("this").append(".").append(it.camelKey)
            sb.append(", ")
        }
        if (sb.endsWith(", ")) {
            sb.delete(sb.lastIndexOf(", "), sb.length)
        }
        sb.append("});\n")


        sb.append("\n").append(spaceStr)
                .append(className).append(".fromJson").append("(Map<String, dynamic> json) {")
                .append(tempSpaceStr)

        orderedList.forEach {
            sb.append("\n").append(tempSpaceStr).append("this").append(".").append(it.value).append(" = ").append("json['").append(it.name).append("'];")
        }

        objectList.forEach {
            sb.append("\n").append(tempSpaceStr).append("this").append(".").append(it.camelKey).append(" = ").append("json['").append(it.value).append("'] != null? ").append(it.key).append(".fromJson(json['").append(it.value).append("']) : null;")
        }

        listList.forEach {
            sb.append("\n").append(tempSpaceStr).append("this").append(".").append(it.camelKey).append(" = ").append("(json['").append(it.value).append("'] as List)!=null?(json['").append(it.value).append("'] as List).map((i) => ").append(it.key).append(".fromJson(i)).toList():null;")
        }

        listBaseList.forEach {
            sb.append("\n")
            sb.append("\n").append(tempSpaceStr).append("List<dynamic> ").append(it.camelKey).append("List").append(" = json['").append(it.value).append("'];")
            sb.append("\n").append(tempSpaceStr).append("this").append(".").append(it.camelKey).append(" = new List();")

            var function = "o.toString()"
            when (it.key) {
                "List<int>" -> function = "int.parse(o.toString())"
                "List<double>" -> function = "double.parse(o.toString())"
                "List<bool>" -> function = "o.toString() == 'true'"
            }

            sb.append("\n").append(tempSpaceStr).append("this").append(".").append(it.camelKey).append(".addAll(").append(it.camelKey).append("List").append(".map((o) => ").append(function).append("));")
        }

        sb.append("\n").append(spaceStr).append("}\n\n")

        sb.append(spaceStr)
                .append("Map<String, dynamic> toJson() {\n").append(tempSpaceStr).append("final Map<String, dynamic> data = new Map<String, dynamic>();")
        orderedList.forEach {
            sb.append("\n").append(tempSpaceStr).append("data['").append(it.name).append("'] = ").append("this").append(".").append(it.value).append(";")
        }

        objectList.forEach {
            sb.append("\n").append(tempSpaceStr).append("data['").append(it.value).append("'] = ").append("this").append(".").append(it.camelKey).append(".toJson();")
        }

        listList.forEach {
            sb.append("\n").append(tempSpaceStr).append("data['").append(it.value).append("'] = ").append("this").append(".").append(it.camelKey).append(" != null?this.").append(it.camelKey).append(".map((i) => i.toJson()).toList():null;")
        }

        listBaseList.forEach {
            sb.append("\n").append(tempSpaceStr).append("data['").append(it.value).append("'] = ").append("this").append(".").append(it.camelKey).append(";")
        }

        sb.append("\n").append(tempSpaceStr).append("return data;\n")
        sb.append(spaceStr).append("}\n")


        tempClasses.forEach { key, value ->
            printClassWithParams(value, space + 2, key)
        }

        return sb.toString()
    }

    private fun buildClasses(): String {
        val sb = StringBuilder()

        classes.forEach { key, value ->
            sb.append("\n")
                    .append("class").append(" ").append(key).append(" ").append("{").append("\n")
                    .append(printClassWithParams(value, 2, key))
                    .append("}").append("\n")
        }

        return sb.toString()
    }
}
複製程式碼

是不是一個模子刻出來的?大量的函式名、屬性名都是一致的,換行空行也都是一致的

NameValuePair/NamePair

我的

由於我已經將 NameValuePair 用 kotlin 改寫了,下面的程式碼是從 git 提交記錄裡找到的

/**
 * Created by zhangll on 2018/8/3.
 */
public class NameValuePair {
    String name;
    String value;

    public NameValuePair(String name, String value) {
        this.name = name;
        this.value = value;
    }
}
複製程式碼

他的

public class NamePair {
    private String camelKey;
    private String key;
    private String value;

    public NamePair(String camelKey, String key, String value) {
        this.camelKey = camelKey;
        this.key = key;
        this.value = value;
    }

    public String getCamelKey() {
        return camelKey;
    }

    public void setCamelKey(String camelKey) {
        this.camelKey = camelKey;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
複製程式碼

乍一看似乎不一樣,其實他只是多寫了 getter setter,又加了一個駝峰命名功能的欄位。我怎麼看出來一致的呢?因為 NameValuePair 嚴格來說,就應該給屬性命名 name 和 value。而另一個人的類名叫 NamePair,中間偏偏少了個 Value ,屬性命名卻又是 key(不是理論上的 name)、value,這是為什麼呢?明眼人應該一看就知道了。

Param

我的

/**
 * Created by zhangll on 2018/8/3.
 */
public class Param {
    String key;
    String value;
    List<Param> clazz;
    String comment;

    /**
     *
     * @param key 變數名
     * @param object 變數內容
     * @return
     */
    public static Param makeParam(String key, Object object) {
        if (object == null || "null".equals(object.toString())) {
            return new Param("dynamic", key, null, object);
        }

        if (object instanceof JsonObject) {
            JsonObject jsonObject = (JsonObject) object;
            return new Param("object", key, json2Params(jsonObject), jsonObject);
        } else if (object instanceof JsonArray) {
            JsonArray jsonArray = (JsonArray) object;
            if (jsonArray.size() != 0) {
                Object obj = jsonArray.get(0);
                if (obj instanceof JsonObject) {
                    return new Param("list", key, json2Params(jsonArray.get(0).getAsJsonObject()), jsonArray);
                } else if (obj instanceof JsonArray) {
                    return new Param("dynamicList", key, null, object);
                } else {
                    Param temp = makeParam("placeholder", obj);
                    if (temp.key.equals("dynamic")) {
                        return new Param("dynamicList", key, null, object);
                    }
                    return new Param("List<" + temp.key + ">", key, null, object);
                }
            } else {
                return new Param("dynamicList", key, null, object);
            }
        } else if (tryParseBoolean(object)) {
            return new Param("bool", key, null, "true".equals(object.toString()));
        } else if (tryParseInt(object)) {
            return new Param("int", key, null, Integer.parseInt(object.toString()));
        } else if (tryParseLong(object)) {
            return new Param("int", key, null, Long.parseLong(object.toString()));
        } else if (tryParseDouble(object)) {
            return new Param("double", key, null, Double.parseDouble(object.toString()));
        } else if (tryParseFloat(object)) {
            return new Param("double", key, null, Float.parseFloat(object.toString()));
        } else {
            return new Param("String", key, null, object.toString());
        }
    }

    public static List<Param> json2Params(JsonObject jsonObject) {
        List<Param> list = new ArrayList<>();
        for (Object o : jsonObject.entrySet()) {
            Map.Entry entry = (Map.Entry) o;
            list.add(Param.makeParam(entry.getKey().toString(), entry.getValue()));
        }

        return list;
    }

    private static boolean tryParseInt(Object object) {
        try {
            int i = Integer.parseInt(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseLong(Object object) {
        try {
            long i = Long.parseLong(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseDouble(Object object) {
        try {
            double d = Double.parseDouble(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseFloat(Object object) {
        try {
            float f = Float.parseFloat(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseBoolean(Object object) {
        String b = object.toString();
        return Objects.equals(b, "true") || Objects.equals(b, "false");
    }

    public Param(String key, String value, List<Param> clazz, Object content) {
        this.key = key;
        this.value = value;
        this.clazz = clazz;

        if (content == null) return;

        // 註釋處理
        this.comment = value + " : " + content.toString().replaceAll("\n", "");
    }

    @Override
    public String toString() {
        return "Param{" +
                "key='" + key + '\'' +
                ", value='" + value + '\'' +
                ", classes=" + clazz +
                '}';
    }
}
複製程式碼

他的

public class Param {
    String key;
    String value;
    String camelWord;
    List<Param> clazz;

    /**
     *
     * @param key 變數名
     * @param object 變數內容
     * @return
     */
    public static Param makeParam(String key, Object object) {
        if (object instanceof JsonObject) {
            JsonObject jsonObject = (JsonObject) object;
            return new Param("object", key, json2Params(jsonObject));
        } else if (object instanceof JsonArray) {
            JsonArray jsonArray = (JsonArray) object;
            if (jsonArray.size() != 0) {
                Object obj = jsonArray.get(0);
                if (obj instanceof JsonObject) {
                    return new Param("list", key, json2Params(jsonArray.get(0).getAsJsonObject()));
                } else {
                    Param temp = makeParam("placeholder", obj);
                    return new Param("List<" + temp.key + ">", key, null);
                }
            } else {
                return new Param("list", key, null);
            }
        } else if (tryParseBoolean(object)) {
            return new Param("bool", key, null);
        } else if (tryParseInt(object)) {
            return new Param("int", key, null);
        } else if (tryParseLong(object)) {
            return new Param("num", key, null);
        } else if (tryParseDouble(object)) {
            return new Param("double", key, null);
        } else if (tryParseFloat(object)) {
            return new Param("double", key, null);
        } else {
            return new Param("String", key, null);
        }
    }

    public static List<Param> json2Params(JsonObject jsonObject) {
        List<Param> list = new ArrayList<>();
        for (Object o : jsonObject.entrySet()) {
            Map.Entry entry = (Map.Entry) o;
            list.add(Param.makeParam(entry.getKey().toString(), entry.getValue()));
        }

        return list;
    }

    private static boolean tryParseInt(Object object) {
        try {
            int i = Integer.parseInt(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseLong(Object object) {
        try {
            long i = Long.parseLong(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseDouble(Object object) {
        try {
            double d = Double.parseDouble(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseFloat(Object object) {
        try {
            float f = Float.parseFloat(object.toString());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean tryParseBoolean(Object object) {
        String b = object.toString();
        return Objects.equals(b, "true") || Objects.equals(b, "false");
    }

    public Param(String key, String value, List<Param> clazz) {
        this.key = key;
        this.value = value;
        this.camelWord = Utils.toUpperCaseParams(value);
        this.clazz = clazz;
    }

    @Override
    public String toString() {
        return "Param{" +
                "key='" + key + '\'' +
                ", value='" + value + '\'' +
                ", classes=" + clazz +
                '}';
    }
}
複製程式碼

又是一個模子刻出來的。除了我新增了註釋功能和部分 bug 修復導致的程式碼變動,他新增了駝峰命名功能。

Util/Utils

我的

/**
 * Created by zhangll on 2018/8/3.
 */
public class Util {
    /**
     * 將字串複製到剪下板。
     */
    public static void setSysClipboardText(String writeMe) {
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable tText = new StringSelection(writeMe);
        clip.setContents(tText, null);
    }

    // 首字母轉大寫
    public static String toUpperCaseFirstOne(String s){
        if(Character.isUpperCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toUpperCase(s.charAt(0))).append(s.substring(1)).toString();
    }

    // 首字母轉小寫
    public static String toLowerCaseFirstOne(String s){
        if(Character.isLowerCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
    }

    // 將 string 寫入檔案
    public static void writeToFile(Project project, VirtualFile file, String content) {
        Runnable runnable = () -> {
            try {
                file.setBinaryContent(content.getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            }
        };

        WriteCommandAction.runWriteCommandAction(project, runnable);
    }
}
複製程式碼

他的

public class Utils {
    /**
     * 將字串複製到剪下板。
     */
    public static void setSysClipboardText(String writeMe) {
        Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable tText = new StringSelection(writeMe);
        clip.setContents(tText, null);
    }

    // 首字母轉大寫
    public static String toUpperCaseFirstOne(String s) {
        if (s.contains("_")) {
            String[] a = s.split("_");
            StringBuilder builder = new StringBuilder();
            for (String anA : a) {
                if (Character.isUpperCase(anA.charAt(0)))
                    builder.append(anA);
                else
                    builder.append(Character.toUpperCase(anA.charAt(0))).append(anA.substring(1));
            }
            return builder.toString();
        }
        if (Character.isUpperCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toUpperCase(s.charAt(0))).append(s.substring(1)).toString();
    }

    // 下劃線引數轉駝峰
    public static String toUpperCaseParams(String s) {
        if (s.contains("_")) {
            String[] a = s.split("_");
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < a.length; i++) {
                if (i==0){
                    builder.append(a[i]);
                }
                if (Character.isUpperCase(a[i].charAt(0)))
                    builder.append(a[i]);
                else if (i != 0)
                    builder.append(Character.toUpperCase(a[i].charAt(0))).append(a[i].substring(1));
            }

            return builder.toString();
        }
        return s;
    }

    // 首字母轉小寫
    public static String toLowerCaseFirstOne(String s) {
        if (Character.isLowerCase(s.charAt(0)))
            return s;
        else
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
    }

    // 將 string 寫入檔案
    public static void writeToFile(VirtualFile file, String content) {
        try {
            file.setBinaryContent(content.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

寫檔案的程式碼好像有不同?沒關係,我 git 里拉老程式碼出來

// 將 string 寫入檔案
public static void writeToFile(VirtualFile file, String content) {
    try {
        file.setBinaryContent(content.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}
複製程式碼

所以,唯一的不同就是我類頭有註釋,而他多了一個駝峰命名的方法。

放上另一個人的專案地址,有閒心的朋友可以自己去看

github.com/xlyasdasd/F…

總結一下

如果就 copy 一下我的程式碼過去用,其實我肯定也沒什麼,按道理來說作為一個程式猿我還應該覺得開心才是。

但是 copy 了我整個專案,又極其不專業的只改了幾個不痛不癢的地方,改得此地無銀三百兩,留著專案結構、類命名、方法命名、空行、換行這些個人風格濃重的東西不動,我是 Kotlin 寫的地方他就是 Kotlin,我用 Java 寫的地方他就是 Java, 難道是因為核心邏輯看不懂不敢改動嗎?既然能自己加上駝峰命名的功能,想來也不至於。

如果只是 copy 專案也就罷了,他甚至還發布到了全家桶外掛的官網,如果不是因為下載量比我自己的外掛還高,恐怕這事我到死都不會知道。

當然,他沒什麼錯,是我自己沒給開源專案加上 LICENSE。但想來就算我有 LICENSE,按他能去釋出外掛這個勇氣來看,只怕也是不在乎的。

有朋友跟我說,我應該開心,應該有成就感,因為至少這證明我做的東西是有價值的,但我覺得事情不應該這麼去看待。我花了心思去寫出來的東西,我心肝情願的開源,我心肝情願的給別人用,但是拿了我的心血去換個包裝貼上他自己的牌子,這誰能接受?

作為一個喜歡 flutter 的開發者,我也不至於因為這事就喪了氣,儘管也付出了不少時間,做了不少迭代,但畢竟只是一個小玩具專案而已。只是這次被好好的上了一課,以後不論多小的專案,LICENSE 是一定不能少的。

最後,Flutter 真香!

相關文章