My-Blog icon indicating copy to clipboard operation
My-Blog copied to clipboard

Kotlin 吐槽大会

Open codefollower opened this issue 7 years ago • 10 comments

因为不满意 JavaScript 以及相关工具链,所以想换个 JVM 系的语言写前端, 选来选去选了 Kotlin,最初期望很大,试用了几个月后挺失望的,憋得难受,不吐不快。

一开始想用 Kotlin 写个能在前端跑的 html parser,因为已经有一个用 Java 开发的 jsoup了,所以就不想从头造了,反正 Java 代码能转成 Kotlin 代码,正好可以从一个现有成熟 Java 项目的角度来审视 Kotlin 。

每个回贴说一个槽点。

codefollower avatar Aug 07 '18 15:08 codefollower

将现有 Java 代码用 Kotlin 重写后并不是每个项目的代码量都能减少 30%

以 jsoup 为例,用 IntelliJ IDEA 2018.1.1 (Community Edition) 转成 Kotlin 后,Java 版的核心代码是 680kb,而 Kotlin 版的是 650kb,只少了 30kb,连个零头都不到。

还试了另一个 Java 开源项目: jericho-html,转成 Kotlin 后代码量也没降多少。

单纯拿 Java 跟 Kotlin 同类的语言特性来比较代码量是没意义的,对于一个成熟的项目来说,因为绝大多数代码都是用最基础的语言特性来写的,这些特性在 Java 和 Kotlin 两者之间并没有多大的区别。

codefollower avatar Aug 07 '18 15:08 codefollower

不要指望把现有的 Java 代码自动转成 Kotlin 后没有任何错误

把 jsoup、 jericho-html 这两个 Java 项目转成 Kotlin 后就存在大量的错误。

codefollower avatar Aug 07 '18 15:08 codefollower

还不支持'\f'转义字符

需要用'\u000C'这样的格式替换'\f'。

codefollower avatar Aug 07 '18 15:08 codefollower

null 检测是个糟糕的语言特性

把现有的 Java 代码自动转成 Kotlin 后最多的问题就是 null 检测问题,多到让人抓狂。

随便拿 jsoup 的代码来举两个例子: private final String[] stringCache = new String[512] 被转成 private val stringCache = arrayOfNulls<String>(512) 把 stringCache 作为参数调用其他方法时,这些方法的参数必须使用 Array<String?> 类型,但是 IDEA 转成 Kotlin 后用的是 Array<String>。从这个例子不但看出 IDEA 足够弱智,而且转换后的代码包括修正后的都啰嗦难看之极。

第二个例子: 像 org.jsoup.parser.TreeBuilder 这种抽象类,有很多实例字段并不是在构造函数中初始化的,而是通过一个类似init的方法来统一初始化,那么这些字段转成 Kotlin 后要不要加问号?IDEA 对于这个类中的代码场景不知所措,我至今都没有用 Kotlin 的思维修正这个类的代码问题。

codefollower avatar Aug 07 '18 16:08 codefollower

啰嗦的 open 关键字

在 Java 中,当一个类/方法用 protected/public 修饰并且没有final时就代表可继承或覆盖, 特别的,按惯例当一个方法用 protected 修饰时就已经暗示原作者已经考虑到被子类覆盖的问题了,而此时又要加上 open,对比原有的 Java 代码显得很啰嗦。

codefollower avatar Aug 07 '18 16:08 codefollower

用companion object来模拟static成员的行为很糟糕

存在大量使用 Logger 的 Java 代码,仅仅是下面一行代码: private static final Logger logger = LoggerFactory.getLogger(XXX.class); 使用 companion object 不但啰嗦,还浪费内存。

codefollower avatar Aug 07 '18 16:08 codefollower

在while循环条件中不能使用赋值表达式

比如,在 Java 中可以像下面这样使用赋值表达式:

    static void crossStreams(final InputStream in, final OutputStream out) throws IOException {
        final byte[] buffer = new byte[bufferSize];
        int len;
        while ((len = in.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
    }

IDEA 转成 Kotlin 代码后会报错,得像下面这样修正:

    @Throws(IOException::class)
    internal fun crossStreams(`in`: InputStream, out: OutputStream) {
        val buffer = ByteArray(bufferSize)
        var len: Int
        len = `in`.read(buffer)
        while (len != -1) {
            out.write(buffer, 0, len)
            len = `in`.read(buffer)
        }
    }

codefollower avatar Aug 08 '18 00:08 codefollower

对象声明中的代码不能访问嵌套类的私有成员

像 jsoup 中的 org.jsoup.helper.DataUtil 只是一个工具类,所有成员都是 static 的,转成 Kotlin 后变成了一个对象声明,原本它有一个如下的嵌套类:

    private static class BomCharset {
        private final String charset;
        private final boolean offset;

        public BomCharset(String charset, boolean offset) {
            this.charset = charset;
            this.offset = offset;
        }
    }

用 IDEA 转成 Kotlin 代码后变成了这样:

    private class BomCharset(private val charset: String, private val offset: Boolean)

DataUtil 的其他代码引用 bomCharset.offset 时就会报错,需要去掉 private。

codefollower avatar Aug 08 '18 00:08 codefollower

让人困惑的范型

jsoup 中有如下扩展了 java.util.ArrayList 的类:

package org.jsoup.helper;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Implementation of ArrayList that watches out for changes to the contents.
 */
public abstract class ChangeNotifyingArrayList<E> extends ArrayList<E> {
    public ChangeNotifyingArrayList(int initialCapacity) {
        super(initialCapacity);
    }

    public abstract void onContentsChanged();

    @Override
    public E set(int index, E element) {
        onContentsChanged();
        return super.set(index, element);
    }

    @Override
    public boolean add(E e) {
        onContentsChanged();
        return super.add(e);
    }

    @Override
    public void add(int index, E element) {
        onContentsChanged();
        super.add(index, element);
    }

    @Override
    public E remove(int index) {
        onContentsChanged();
        return super.remove(index);
    }

    @Override
    public boolean remove(Object o) {
        onContentsChanged();
        return super.remove(o);
    }
    //......
    @Override
    public boolean removeAll(Collection<?> c) {
        onContentsChanged();
        return super.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        onContentsChanged();
        return super.retainAll(c);
    }

}

用 IDEA 转成 Kotlin 代码后一开始是这样(错误当然一堆了):

package org.jsoup.helper

import java.util.ArrayList

/**
 * Implementation of ArrayList that watches out for changes to the contents.
 */
abstract class ChangeNotifyingArrayList<E>(initialCapacity: Int) : ArrayList<E>(initialCapacity) {

    abstract fun onContentsChanged()

    override operator fun set(index: Int, element: E?): E {
        onContentsChanged()
        return super.set(index, element)
    }

    override fun add(e: E?): Boolean {
        onContentsChanged()
        return super.add(e)
    }

    override fun add(index: Int, element: E?) {
        onContentsChanged()
        super.add(index, element)
    }

    override fun remove(index: Int): E {
        onContentsChanged()
        return super.removeAt(index)
    }

    override fun remove(o: Any?): Boolean {
        onContentsChanged()
        return super.remove(o)
    }
    //......
    override fun removeAll(c: Collection<*>): Boolean {
        onContentsChanged()
        return super.removeAll(c)
    }

    override fun retainAll(c: Collection<*>): Boolean {
        onContentsChanged()
        return super.retainAll(c)
    }
}

修正了大部分问题后是这样(注释那段还不知问题原因):

package org.jsoup.helper

import java.util.ArrayList

/**
 * Implementation of ArrayList that watches out for changes to the contents.
 */
abstract class ChangeNotifyingArrayList<E>(initialCapacity: Int) : ArrayList<E>(initialCapacity) {

    abstract fun onContentsChanged()

    override operator fun set(index: Int, element: E): E {
        onContentsChanged()
        return super.set(index, element)
    }

    override fun add(e: E): Boolean {
        onContentsChanged()
        return super.add(e)
    }

    override fun add(index: Int, element: E) {
        onContentsChanged()
        super.add(index, element)
    }

//    override fun remove(index: Int): E {
//        onContentsChanged()
//        return super.removeAt(index)
//    }

    override fun remove(o: E): Boolean {
        onContentsChanged()
        return super.remove(o)
    }
    //......
    override fun removeAll(c: Collection<E>): Boolean {
        onContentsChanged()
        return super.removeAll(c)
    }

    override fun retainAll(c: Collection<E>): Boolean {
        onContentsChanged()
        return super.retainAll(c)
    }
}

codefollower avatar Aug 08 '18 01:08 codefollower

大佬,你终于想起自己的账号了吗...

yyancy avatar Aug 27 '18 03:08 yyancy