buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

一道面试题:计算时间偏移量,怎么设计你的程序?

计算时间偏移量,例如,计算当前时间向前偏移 30 秒的时间,我们利用java.util.Calendar很容易实现。

    Calendar cal = Calendar.getInstance();
    cal.setTime(new Date());
    cal.add(Calendar.SECOND, -30);
    System.out.println(cal.getTime());

 

我在组织公司的技术招聘面试的时候,关于程序设计,有问过应聘者这样的问题。

那么,我们怎么封装这么一个工具类呢?这个工具类提供哪些工具方法呢?每个方法又当怎么实现呢?

下面这段优秀的代码节选自hutool-DateUtil(hutool-all-4.5.18.jar ,maven坐标:cn.hutool:hutool-all:4.5.18),香香的,甜甜的,pretty,graceful,pretty graceful.

坦白说,写出来这个util并不难,你可以写出来你的代码,然后做个比较,看看与优秀代码的差距。

    // --------------------------------------------------- Offset for now
    /**
     * 昨天
     * 
     * @return 昨天
     */
    public static DateTime yesterday() {
        return offsetDay(new DateTime(), -1);
    }

    /**
     * 明天
     * 
     * @return 明天
     * @since 3.0.1
     */
    public static DateTime tomorrow() {
        return offsetDay(new DateTime(), 1);
    }

    /**
     * 上周
     * 
     * @return 上周
     */
    public static DateTime lastWeek() {
        return offsetWeek(new DateTime(), -1);
    }

    /**
     * 下周
     * 
     * @return 下周
     * @since 3.0.1
     */
    public static DateTime nextWeek() {
        return offsetWeek(new DateTime(), 1);
    }

    /**
     * 上个月
     * 
     * @return 上个月
     */
    public static DateTime lastMonth() {
        return offsetMonth(new DateTime(), -1);
    }

    /**
     * 下个月
     * 
     * @return 下个月
     * @since 3.0.1
     */
    public static DateTime nextMonth() {
        return offsetMonth(new DateTime(), 1);
    }

    /**
     * 偏移毫秒数
     * 
     * @param date 日期
     * @param offset 偏移毫秒数,正数向未来偏移,负数向历史偏移
     * @return 偏移后的日期
     */
    public static DateTime offsetMillisecond(Date date, int offset) {
        return offset(date, DateField.MILLISECOND, offset);
    }

    /**
     * 偏移秒数
     * 
     * @param date 日期
     * @param offset 偏移秒数,正数向未来偏移,负数向历史偏移
     * @return 偏移后的日期
     */
    public static DateTime offsetSecond(Date date, int offset) {
        return offset(date, DateField.SECOND, offset);
    }

    /**
     * 偏移分钟
     * 
     * @param date 日期
     * @param offset 偏移分钟数,正数向未来偏移,负数向历史偏移
     * @return 偏移后的日期
     */
    public static DateTime offsetMinute(Date date, int offset) {
        return offset(date, DateField.MINUTE, offset);
    }

    /**
     * 偏移小时
     * 
     * @param date 日期
     * @param offset 偏移小时数,正数向未来偏移,负数向历史偏移
     * @return 偏移后的日期
     */
    public static DateTime offsetHour(Date date, int offset) {
        return offset(date, DateField.HOUR_OF_DAY, offset);
    }

    /**
     * 偏移天
     * 
     * @param date 日期
     * @param offset 偏移天数,正数向未来偏移,负数向历史偏移
     * @return 偏移后的日期
     */
    public static DateTime offsetDay(Date date, int offset) {
        return offset(date, DateField.DAY_OF_YEAR, offset);
    }

    /**
     * 偏移周
     * 
     * @param date 日期
     * @param offset 偏移周数,正数向未来偏移,负数向历史偏移
     * @return 偏移后的日期
     */
    public static DateTime offsetWeek(Date date, int offset) {
        return offset(date, DateField.WEEK_OF_YEAR, offset);
    }

    /**
     * 偏移月
     * 
     * @param date 日期
     * @param offset 偏移月数,正数向未来偏移,负数向历史偏移
     * @return 偏移后的日期
     */
    public static DateTime offsetMonth(Date date, int offset) {
        return offset(date, DateField.MONTH, offset);
    }

    /**
     * 获取指定日期偏移指定时间后的时间
     * 
     * @param date 基准日期
     * @param dateField 偏移的粒度大小(小时、天、月等){@link DateField}
     * @param offset 偏移量,正数为向后偏移,负数为向前偏移
     * @return 偏移后的日期
     */
    public static DateTime offset(Date date, DateField dateField, int offset) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(dateField.getValue(), offset);
        return new DateTime(cal.getTime());
    }

    // ------------------------------------ Offset end ----------------------------------------------

 

为什么说这么代码比较香呢?你品,你细品!

  • 易读。注意各个方法尤其是以“offset”开头的方法的签名,包括方法名、方法参数,包括javadoc,相当清晰,易读易理解。另外,这几个方法整体来看,像极了我们母语中的排比句。
  • 丰富。按不同的时间单位(如秒、分钟、小时、天、周和月)偏移日期时间值,定义了丰富的方法,各种姿势满足你。
  • 易用。除了offsetMinute/offsetSecond等offsetXxx方法,还提供了yesterday / tomorrow / lastWeek / nextWeek / lastMonth /nextMonth等拿来即用的方法,不必再调用offsetXxx。
  • 简洁。计算时间偏移量的算法是相同的,所以,这些方法内部均调用一个通用的 offset 方法,该方法使用 DateField 枚举值指定要偏移的时间单位和偏移量。
  • 包容。注意最后那个public的offset(Date date, DateField dateField, int offset)方法,当上面的计算时间偏移量的方法无法满足使用要求时(当然,已经是应有尽有了),你就可以用它了。

 

同样,关于本地缓存工具,分享一段我曾经编写的基于hutool-cache封装的LocalCacheUtil工具类。

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 本地缓存工具
 */
@Slf4j
public class LocalCacheUtil {
    private static Cache<String, Object> lfuCache = CacheUtil.newLFUCache(256, TimeUnit.MINUTES.toMillis(30));
    private static Cache<String, Object> timedCache = CacheUtil.newTimedCache(TimeUnit.DAYS.toMillis(1));//过期时间给个默认值

    /**
     * 从本地缓存获取数据。如果没有,则设置。(策略:最少使用原则)
     *
     * @param key
     * @param supplier
     * @param <T>
     * @return
     *
     * @see #lfuCache
     */
    public static <T> T getCache(String key, Supplier<T> supplier) {
        return getCache(key, false, supplier);
    }

    /**
     * 从本地缓存获取数据。如果没有,则设置。(策略:最少使用原则)
     *
     * @param key
     * @param cacheNullOrEmpty 是否缓存null或空集合
     * @param supplier
     * @param <T>
     * @return
     *
     * @see #lfuCache
     */
    public static <T> T getCache(String key, boolean cacheNullOrEmpty, Supplier<T> supplier) {
        return getCache(lfuCache, key, null, cacheNullOrEmpty, supplier);
    }

    /**
     * 获取缓存。如果没有,则设置
     *
     * @param key
     * @param seconds
     * @param supplier 缓存数据提供者
     * @param <T>
     * @return
     */
    public static <T> T getCache(String key, long seconds, Supplier<T> supplier) {
        return getCache(key, seconds, false, supplier);
    }

    /**
     * 删除缓存
     *
     * @param key 缓存key
     */
    public static void removeCache(String key) {
        timedCache.remove(key);
    }

    /**
     * 获取缓存。如果没有,则设置
     *
     * @param key
     * @param seconds
     * @param cacheNullOrEmpty 是否缓存null或空集合
     * @param supplier         缓存数据提供者
     * @param <T>
     * @return
     */
    public static <T> T getCache(String key, long seconds, boolean cacheNullOrEmpty, Supplier<T> supplier) {
        return getCache(timedCache, key, seconds, cacheNullOrEmpty, supplier);
    }

    private static <T> T getCache(Cache<String, Object> myCache, String key, Long seconds, boolean cacheNullOrEmpty, Supplier<T> supplier) {
        if (myCache.containsKey(key)) {
            return (T) myCache.get(key);
        } else {
            T result = supplier.get();
            if (!cacheNullOrEmpty) {
                if (result == null) {
                    log.info("设置缓存---value为null,不设置--- key={}", key);
                    return null;
                } else if (result instanceof Collection && ((Collection) result).size() == 0) {
                    log.info("设置缓存---value是个空集合,不设置--- key={}", key);
                    return null;
                }
            }
            log.info("设置缓存 key={}", key);
            if (seconds == null) {
                myCache.put(key, result);
            } else {
                myCache.put(key, result, TimeUnit.SECONDS.toMillis(seconds));
            }
            return result;
        }
    }

}
View Code

 

另外,在这个DateUtil工具类中,有一个弃用的offsetDate方法如下。

    /**
     * 获取指定日期偏移指定时间后的时间
     * 
     * @param date 基准日期
     * @param dateField 偏移的粒度大小(小时、天、月等){@link DateField}
     * @param offset 偏移量,正数为向后偏移,负数为向前偏移
     * @return 偏移后的日期
     * @deprecated please use {@link DateUtil#offset(Date, DateField, int)}
     */
    @Deprecated
    public static DateTime offsetDate(Date date, DateField dateField, int offset) {
        return offset(date, dateField, offset);
    }

作为一个不断迭代升级的Java工具库,显然hutool不能轻易将之前的方法直接去掉,这会遭到骂娘的。因此,hutool的开发者标记了@Deprecated,并在方法的javadoc里明确指引出来,调用另一个offset(Date, DateField, int)重载。--——————这是一个优秀编码风格,标记弃用,请向使用者描述背景(弃用原因)并告知使用者应该怎么办。(正如阿里巴巴Java开发手册中的【强制】规范:接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。)

那么,现在,我们来思考一下:为什么弃用这个offsetDate方法改用offset方法呢?欢迎评论区交流!

我在公司内部的软件系统中,一直在践行关于弃用方法的这一优秀编码行为。只是后来,随着开发经验和意识的增强,在实践过程中做了一些调整。即,我不再一味地标记弃用,而是斩草除根,对于不合理的方法,优秀采用的方式是直接干掉方法并修改对方法的调用,这么做的出发点有三:①我们是中小型内部企业应用系统,工具或组件都是对内使用,具备内部修改的条件;②团队成员编码意识良莠不齐,被明确标记了弃用的方法,有时仍被使用;③最好的方式是一次做好,避免时间一长自己都忘了这些冗余代码了。

 

The End.

 

【EOF】欢迎大家关注微信公众号「靠谱的程序员」,让我们一起做靠谱的程序员。

posted on 2023-07-20 09:00  buguge  阅读(250)  评论(0编辑  收藏  举报