V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
NULL2020
V2EX  ›  Java

请教下,项目中静态方法过多有什么缺点?

  •  1
     
  •   NULL2020 · 2021-03-29 16:23:34 +08:00 · 6439 次点击
    这是一个创建于 1336 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近被朋友拉拢一起搞个小项目,我算是目前最后入伙的。

    前几天提了个建议,把 redis 的工具类改成静态方法的形式(现在是以 spring bean 的形式交给容器管理),方便调用。结果另一位后端说不要,静态方法太多不好,问他为什么,说叫自己去看 JVM (黑人问号脸)。

    我思考几天,也 google 了下,也没见着有特别有说服力的说法,自己勉强想到了一个点,那可能就是会导致 JVM 静态方法区容量过大,最终引起 OOM ?

    但是,说实话,一个小项目,也就那点代码量,连一个工具类这点内存都要省了吗?

    第 1 条附言  ·  2021-03-30 16:31:53 +08:00
    1. redis 连接使用的就是 Spring 托管的 RedisTemplate,从容器里获取到实例赋值给静态变量。

    2. 静态方法只是对 redisTemplate 的基础命令进行了一次简单的封装,当然,其实这个工具类的封装用依赖注入的方式或许是更好的选择,只是没有 [黑魔法] 的需求,所以选择了调用更方便的静态方法,仅此而已。

    3. 从设计模式、OO 的角度来看,static 不是一个好的选择。

    70 条回复    2021-04-12 17:20:31 +08:00
    kop1989
        1
    kop1989  
       2021-03-29 16:31:25 +08:00
    完全没必要纠结。

    我问你 1+1 等于几,你肯定会说等于 2 。而不会让我去用计算器。
    因为你清楚 1+1 真的等于 2 。
    同理。
    Jooooooooo
        2
    Jooooooooo  
       2021-03-29 16:32:16 +08:00
    不要过早 /过度优化.
    NULL2020
        3
    NULL2020  
    OP
       2021-03-29 16:37:19 +08:00
    @Jooooooooo 这段时间合作下来,我就觉得他有点这个问题,前面搞了两个月,注册登录权限都还没搞好,网上随便拿个轮子改一下,顶多一周搞定。

    还有,一上来就整了套 spring cloud 微服务,汗。。
    opengps
        4
    opengps  
       2021-03-29 16:39:24 +08:00 via Android
    现在的硬件资源很充沛,不用过早纠结
    你大量静态会增加初始化时候的内存占用,但是实际上一个不合理的全表查询就已经吞掉了大量的内存,所以这个优化的必要性,其实根本不必要这么早
    winnerczwx
        5
    winnerczwx  
       2021-03-29 16:41:30 +08:00
    假设他说的是对的

    基于以上假设, 从程序员角度他是优秀的

    从项目创始人角度, 他是不合格的

    你们需要的是用最短时间做出 MVP 版本, 而不是纠结用不用静态类
    securityCoding
        6
    securityCoding  
       2021-03-29 16:51:46 +08:00 via Android   ❤️ 1
    为什么要把 redis 操作方法静态化
    kiripeng
        7
    kiripeng  
       2021-03-29 16:53:35 +08:00
    只考虑技术相当于,经济学家不懂政治。。。。
    quan01994
        8
    quan01994  
       2021-03-29 16:55:59 +08:00
    先把功能做出来,不要过早的优化。
    wolfie
        9
    wolfie  
       2021-03-29 16:57:32 +08:00
    为什么要把 redis 操作方法静态化 +1
    kaiki
        10
    kaiki  
       2021-03-29 16:59:33 +08:00
    先跑起来,手头没活就说在优化,以后顶不住就提桶跑路。
    qwerthhusn
        11
    qwerthhusn  
       2021-03-29 17:05:10 +08:00
    RedisTemplate 不好使? Redisson ? Jedis ?都可以直接用啊
    zjsxwc
        12
    zjsxwc  
       2021-03-29 17:07:54 +08:00   ❤️ 2
    以我有限的 java 经验来说,
    静态方法缺点是不能黑魔法,
    容器好像都是基于动态代理实现 AOP 之类的黑魔法,
    反射估计也不能修改静态方法的行为,
    所以我写 java 是尽量避免使用静态方法。
    uselessVisitor
        13
    uselessVisitor  
       2021-03-29 17:10:53 +08:00
    为什么要静态化?托管不是很好?
    arthas2234
        14
    arthas2234  
       2021-03-29 17:10:58 +08:00
    @NULL2020
    项目急得话,还是怎么快怎么来,但前提也是要封装好,方便以后重构
    还有一上来就整微服务是什么鬼,项目是有多大哦。别到时候整了半天,项目都黄了。难道是想借这个项目练手
    GM
        15
    GM  
       2021-03-29 17:17:39 +08:00
    public static 方法 == 全局函数
    public static 变量 == 全局变量

    看到这种代码我一般都呸一下,我就 tmd 想调用你一个方法,你 tmd 自己把整个系统都串起来跑。

    这种代码想做单元测试?做梦比较合适。
    ouyc
        16
    ouyc  
       2021-03-29 17:22:39 +08:00
    就算省能省到哪里去,几个静态方法难道还能撑破 JVM 。麻烦你问下那位后端,我想知道为什么静态方法多了不好,竟然还是关于 JVM 的
    NULL2020
        17
    NULL2020  
    OP
       2021-03-29 17:24:10 +08:00
    @securityCoding
    @wolfie
    @qwerthhusn

    bean 的方式是在每个使用的地方引入,并且在业务代码处一堆重复的代码:
    redisTemplate.opsForXXX()

    而静态方法的方便之处就是不需要引入,把 redisTemplate.opsForXXX() 这些重复代码都封装在一个方法里了

    @beichenhpy 各有各的好处吧,redis 操作本来就是一些基本命令操作,类似于工具方法,所以习惯使用静态方法
    NULL2020
        18
    NULL2020  
    OP
       2021-03-29 17:26:26 +08:00
    @securityCoding
    @wolfie

    反问,为什么不要静态化?望赐教
    NULL2020
        19
    NULL2020  
    OP
       2021-03-29 17:27:23 +08:00
    @GM 原谅我看不懂你想表达什么,有什么见解欢迎分享
    pushback
        20
    pushback  
       2021-03-29 17:29:46 +08:00
    先做出来,再优化,起步太高(指考虑太多)在小项目里只会拖延进度
    shenjinpeng
        21
    shenjinpeng  
       2021-03-29 17:30:23 +08:00
    基于面向对象还是函数式编程 ? 如果以后要对缓存 /db driver 进行封装, 需要把 redis 也封装进去, 那之前写的不就废了 .
    cccssss
        22
    cccssss  
       2021-03-29 17:33:48 +08:00
    静态方法怎么连的 redis ?静态工具类里写死的么?连接池咋解决的?
    chendy
        23
    chendy  
       2021-03-29 17:35:02 +08:00
    问题是为什么不用 spring 的 bean 的方式?
    静态不方便加黑魔法,比如切面,比如 mock,等等等等
    tabris17
        24
    tabris17  
       2021-03-29 17:35:43 +08:00
    不至于影响 JVM 效率,不过耦合度太高了,不符合面向接口编程的模式
    NULL2020
        25
    NULL2020  
    OP
       2021-03-29 17:40:53 +08:00
    @chendy 上面说了,因为方便。

    看了几个回复,比较中肯的说法大概是 [不方便加黑魔法,比如切面] 之类的了,可能我暂时还没遇到有这样的业务场景吧,所以影响了我的选择
    NULL2020
        26
    NULL2020  
    OP
       2021-03-29 17:42:12 +08:00
    @cccssss 连接用的是 Spring 容器里的 redisTemplate,静态方法只是对它进行的简单封装
    cccssss
        27
    cccssss  
       2021-03-29 17:54:56 +08:00
    @NULL2020
    redisTemplate 咋注入到静态类?从 beanFactory 中去获取么?还是静态类加 @Component ?然后想用 redis 静态类时候再注入一下然后再用?
    agdhole
        28
    agdhole  
       2021-03-29 18:20:43 +08:00
    额外造个 facade
    chendy
        29
    chendy  
       2021-03-29 18:27:40 +08:00
    @NULL2020 能说一下方便在什么地方么?
    hefish
        30
    hefish  
       2021-03-29 18:38:09 +08:00
    能一上来搞 springcloud 的,肯定是大神。我这儿一个活动管理系统,也被做成了分布式的,zookeeper 都用上了。 可能工程师是大户人家出身,出手阔绰。
    dqzcwxb
        31
    dqzcwxb  
       2021-03-29 18:38:31 +08:00
    楼主说的是静态获取 redis 对象,而不是静态创建 redis 链接
    简单讲就是把 @Resource 注入变成 RedisaMange.inst()方式获取 redis 操作对象
    二者实际上没有本质区别,都是单例只是获取方式的写法不同
    你同事说的含糊其辞估计他也不懂只是不想改,你想改的理由也不充分
    所以,不改
    Cbdy
        32
    Cbdy  
       2021-03-29 18:40:42 +08:00 via Android
    LZ 同事说得没错,去看看 JDK 源码
    munan56
        33
    munan56  
       2021-03-29 19:29:34 +08:00
    他只是不想改。
    GM
        34
    GM  
       2021-03-29 19:58:45 +08:00
    @NULL2020

    我就问一句:静态函数你怎么注入?如果你们没用依赖注入的话,当我没说过。

    为什么我这么恶心静态函数调用,是因为我维护过一个系统,里面每个 Service 都是静态函数互相调用,任何一个函数调用,都会调用到 B 、C 、D...模块里面的多个静态函数,然后这 B 、C 、D...模块里面的多个静态函数里面,又分别调用 EFG... 里面的 N 个静态函数,总之,一句话,想调用任何一个函数,必须要会启动整个系统,才能成功调用。想简单 mock 一下来测试一个某个函数?不可能,因为全是静态函数调用。
    4771314
        35
    4771314  
       2021-03-29 20:01:27 +08:00
    @GM 那你是捡到包了啊 AA
    NULL2020
        36
    NULL2020  
    OP
       2021-03-29 21:27:54 +08:00 via iPhone
    @GM 你这种是典型的因为一个极端案例而全盘否认一个在某些场景合理的存在
    NULL2020
        37
    NULL2020  
    OP
       2021-03-29 21:28:56 +08:00 via iPhone
    @Cbdy 有高见不妨分享一下,没必要这么隐晦
    Cbdy
        38
    Cbdy  
       2021-03-29 21:41:57 +08:00 via Android
    @NULL2020 没有隐晦啊,JDK 有很多好的实践,可以学习一下
    huijiewei
        39
    huijiewei  
       2021-03-29 21:47:07 +08:00
    业务写静态是有多想不开

    顶多工具类用用静态
    kassadin
        40
    kassadin  
       2021-03-29 22:51:22 +08:00
    写一回测试就差不多 get 到了
    24bit
        41
    24bit  
       2021-03-29 23:06:26 +08:00   ❤️ 1
    感觉无副作用的函数会比较适合写成静态的
    24bit
        42
    24bit  
       2021-03-29 23:24:59 +08:00
    Redis 工具类感觉大概率会依赖外部配置,同时对 Redis 进行修改,写成静态类本质上也就是利用静态全局变量来托管 Redis 对象。

    这和用容器来托管感觉没有太多本质的区别,而且使用容器和可以享受 Spring 带来的很多便利。

    对于 JVM,静态类感觉其实也就是方法区会多点静态常量?而且静态方法可以通过 invokestatic 调用,这个在性能上应该会比 invokevirtual 好一些。
    NULL2020
        43
    NULL2020  
    OP
       2021-03-29 23:33:48 +08:00
    @huijiewei 大概是我把操作 redis 当作工具使用了吧

    @kassadin 单元测试正常使用
    gadsavesme
        44
    gadsavesme  
       2021-03-30 00:03:17 +08:00
    一般上来问题说不清楚,动不动就是自己去看 xxx 的人,大概率自己就是个半吊子。方法区 oom 的我见过,写太多静态方法导致 oom 的我是孤陋寡闻了,几十 m 的内存就够你复制静态方法复制到手抽筋了。
    GM
        45
    GM  
       2021-03-30 10:11:47 +08:00   ❤️ 1
    @NULL2020 我是拿一个极端的案例来说明为什么不要用 public static,你这个使用场景明显就属于不适合使用的场景。

    什么场景适合使用呢?完全无状态的、不依赖、不修改任何外部状态的函数可以使用 public static 。
    GM
        46
    GM  
       2021-03-30 10:12:49 +08:00
    @gadsavesme JVM8 以上不存在方法区 OOM 问题。
    wolfie
        47
    wolfie  
       2021-03-30 10:56:17 +08:00
    @NULL2020 #18
    没必要,且没有优点。什么场景必须由 静态方法 去调用这个工具类。

    封装、继承、多态。
    新需求,需要加 切面 怎么办、需要不同对象 调用时,注入不同工具子类怎么办。
    wolfie
        48
    wolfie  
       2021-03-30 10:58:04 +08:00
    @NULL2020 #17
    你这个封装 跟 static 无关。
    q149072205
        49
    q149072205  
       2021-03-30 11:19:12 +08:00
    static 速度快啊,不用实例化啊。。
    CODEWEA
        50
    CODEWEA  
       2021-03-30 11:31:14 +08:00
    因为你的提出的意见毫无价值,没有修改的必要性
    hantsy
        51
    hantsy  
       2021-03-30 11:33:09 +08:00
    》那可能就是会导致 JVM 静态方法区容量过大,最终引起 OOM ?

    这个是存在的。

    工具类使用 static,要看情况。spring core 中有一个 org.springframework.utils 都是工具类,没必要实例化,用容器管理。
    encro
        52
    encro  
       2021-03-30 13:01:58 +08:00
    赞同 @GM

    修改内部属性和数据的通常都不用静态。
    可能连接池后者单例的都不用静态。


    案例一:

    比如 redis 操作,没必要静态。

    r = new redis();
    r->set();

    因为这里 redis 可能是连接池,也可能是单例,这时候为了维护容易,不要再去做 redis::set 方法,因为静态方法可能跳过构造函数。

    假设有多个 redis 数据库,那么可以是

    r1 = new redis(db1);
    r2 = new redis(db2);

    静态方法可能就成了

    redis::set(key,val,db1)

    用起来就纠结了


    案例二:

    mvc 的 model 里面通常除了 create 方法,都不要静态(静态常量可以有)

    Class Product{

    const STATE_DRAFT =0
    const STATE_PUBLISH =1

    public static function create(data){}

    public function update(data){}

    public function remove(){}
    }

    这时候 update,remove 不静态,是为了减少对外暴露接口,方便代码统一修改。
    create 用静态是因为它返回了 product 实例
    GM
        53
    GM  
       2021-03-30 13:20:14 +08:00
    @encro 要被屎山荼毒过后,才能知道原来依赖注入是很香的,设计模式是真有用的。
    no1xsyzy
        54
    no1xsyzy  
       2021-03-30 13:42:19 +08:00
    神说:过早或过度优化是万恶之源

    过早或过度抽象也差不了多少。
    前期能不封装就不封装,能 Ctrl-C Ctrl-V 决不 Extract function / method
    不过,如果你学过 Haskell 的话你会很清楚如何抽象,这个语言是抽象适度程度的训练。

    简单地说,面向对象的核心是印欧语系的主谓结构,class 是本体论的映射,interface 是认知论的映射。

    @winnerczwx 你需要注意一下一个背景条件:当前已经完成了一个实现。因此从项目创始人角度来说,不改才是对的。

    @GM 静态函数完全可以做出类似依赖注入的效果,把需要注入的部分作为参数显式传递。这大约就是 Functor ?
    C 时代就是这么写的,Go 的方法书写起来也差不多这个感觉,至于 Python 的话 self 都是显式传递的……
    no1xsyzy
        55
    no1xsyzy  
       2021-03-30 13:53:54 +08:00
    @no1xsyzy class 是范畴论的映射,object 是本体论的映射,interface 和 trait 是认知论的两种学说的映射(虽然 Java 的 interface 其实是 trait ?),前者我知道是 “鸭子定律” 的映射,后者不清楚有什么称呼。

    提问:你的 redis 操作是范畴论下的操作,还是本体论下的操作?
    存在一个相关范畴,你在该范畴下描述一个客观规律?还是说,这是存在一个东西,你在描述一个东西的行为?
    HolmLoh
        56
    HolmLoh  
       2021-03-30 14:46:27 +08:00
    如果我没记错的话
    java8 已经用元空间替代掉了原来的永久代
    元空间是用的直接内存,只要你的机器够,就不会 OOM

    所以因为内存原因而不能用静态方法是没有道理的
    1109599636
        57
    1109599636  
       2021-03-30 14:56:04 +08:00
    我一个写 go 和 py 的也想知道为什么,但是 50 多楼下来没有人能给出一个有说服力的答案。。。
    GM
        58
    GM  
       2021-03-30 15:16:09 +08:00
    @no1xsyzy 那你累不累啊?既然已经是用 spring 框架了,老老实实用 spring 的依赖注入,不香吗?
    GM
        59
    GM  
       2021-03-30 15:17:38 +08:00
    @1109599636 我在 15 楼已经给出了。
    no1xsyzy
        60
    no1xsyzy  
       2021-03-30 15:29:21 +08:00
    @GM 因为用的语言太多了,所以习惯了自己构造抽象(负迁移警告)
    FreeEx
        61
    FreeEx  
       2021-03-30 15:31:27 +08:00
    问题不在于静态方法,而在于上了 spring 的船为什么不用 RedisTemplate ?
    NULL2020
        62
    NULL2020  
    OP
       2021-03-30 15:43:20 +08:00
    @FreeEx 仔细审题,用的就是 RedisTemplate,我的工具类只是把它再进行一次简单的封装,使用静态方法的方式调用。

    @encro
    @no1xsyzy
    两位大佬洋洋洒洒,我一句没看懂。。/doge
    NULL2020
        63
    NULL2020  
    OP
       2021-03-30 15:44:42 +08:00
    @CODEWEA 你的回复我也没看出来有什么价值
    Marszm
        64
    Marszm  
       2021-03-30 15:46:08 +08:00
    幸好,我们这边就我懂 redis..我想怎么用怎么用..
    namelosw
        65
    namelosw  
       2021-03-30 16:10:47 +08:00
    把 redis 的工具类改成静态方法的形式(现在是以 spring bean 的形式交给容器管理),方便调用

    > 我怀疑你们很少写测试
    namelosw
        66
    namelosw  
       2021-03-30 16:16:41 +08:00
    @no1xsyzy 其实 Haskell 也用依赖注入,只不过是走 Reader Monad 或者 Tagless Final 的形式。

    他这个问题本质上就是回答系统会有几个 Redis (比如有假的 Redis 用来测试,或者用来做六边形架构,下面可能会替换多套实现),如果大于 1,就走依赖注入,如果等于 1 就无所谓。
    chocotan
        67
    chocotan  
       2021-03-30 16:18:13 +08:00
    封不封装跟静态方法一点关系都没有
    est
        68
    est  
       2021-03-30 16:21:04 +08:00
    redis 还是建议不要搞成静态方法。因为这玩意一般还要挂一个连接池。
    encro
        69
    encro  
       2021-03-30 18:39:19 +08:00
    通常来说就是调用静态方法创建实例,然后调用实例的方法来修改、摧毁自身。

    选择这样:

    canvas= new canvas();
    duck=new duck();
    duck.setColor(red);
    duck.run(speed);
    duck.eat();
    duck.die();
    cavas.add(duck);


    而不是这样:

    canvas= new canvas();
    duck=new duck();
    addDuckToCanvas(duck,canvas);

    更不要:

    moveDuck(canvas,duck,speed)


    为什么?因为实际情况可能是:

    canvas= new canvas();
    duck=new duck(color,speed,size,direction);
    canvas.add(duck);

    这是鸭子的运动由自己控制,而不是画布控制。

    就如现实世界:

    交警根据司机是否违规开罚单,司机自己管开车。

    每个类做好自己的事情情况下,尽量减少外部条件依赖。

    外部依赖越少,代码越好维护。
    CantSee
        70
    CantSee  
       2021-04-12 17:20:31 +08:00
    直接 @Component 梭哈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2693 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 15:18 · PVG 23:18 · LAX 07:18 · JFK 10:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.