就我那點垃圾程式碼竟然也有人要抄?
小弟不才,去年學習 flutter 的過程中,搞過幾個小玩具,其中就有一個將 json 資料轉換成 dart class 的 idea 外掛。
不想點的朋友可以看一下 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(); } } 複製程式碼
所以,唯一的不同就是我類頭有註釋,而他多了一個駝峰命名的方法。
放上另一個人的專案地址,有閒心的朋友可以自己去看
總結一下
如果就 copy 一下我的程式碼過去用,其實我肯定也沒什麼,按道理來說作為一個程式猿我還應該覺得開心才是。
但是 copy 了我整個專案,又極其不專業的只改了幾個不痛不癢的地方,改得此地無銀三百兩,留著專案結構、類命名、方法命名、空行、換行這些個人風格濃重的東西不動,我是 Kotlin 寫的地方他就是 Kotlin,我用 Java 寫的地方他就是 Java, 難道是因為核心邏輯看不懂不敢改動嗎?既然能自己加上駝峰命名的功能,想來也不至於。
如果只是 copy 專案也就罷了,他甚至還發布到了全家桶外掛的官網,如果不是因為下載量比我自己的外掛還高,恐怕這事我到死都不會知道。
當然,他沒什麼錯,是我自己沒給開源專案加上 LICENSE。但想來就算我有 LICENSE,按他能去釋出外掛這個勇氣來看,只怕也是不在乎的。
有朋友跟我說,我應該開心,應該有成就感,因為至少這證明我做的東西是有價值的,但我覺得事情不應該這麼去看待。我花了心思去寫出來的東西,我心肝情願的開源,我心肝情願的給別人用,但是拿了我的心血去換個包裝貼上他自己的牌子,這誰能接受?
作為一個喜歡 flutter 的開發者,我也不至於因為這事就喪了氣,儘管也付出了不少時間,做了不少迭代,但畢竟只是一個小玩具專案而已。只是這次被好好的上了一課,以後不論多小的專案,LICENSE 是一定不能少的。