程式進化史

2024-04-16 工作雜記 groovy CyclomaticComplexity

筆記一下這些年我寫的糞 code 的優化流程

一開始寫的

被檢測為 CyclomaticComplexity 。程式如下:

package work.pollochang.twgcb

import grails.gorm.transactions.Transactional
import grails.web.servlet.mvc.GrailsParameterMap
import org.hibernate.criterion.CriteriaSpecification
import org.hibernate.type.StandardBasicTypes
import work.pollochang.util.PFilterResult
import work.pollochang.util.PFilterType

/**
 * TWGCB 資料處理
 */
@Transactional
class TwGcbService {

    /**
     * 查詢
     * @param params
     * @param filterType
     * @return 查詢結果
     */
    PFilterResult filter(
            GrailsParameterMap params,
            PFilterType filterType = PFilterType.DEFAULT
    ) {
        PFilterResult pFilterResult = new PFilterResult()

        params.max = (params.max ?: '10').toInteger()
        params.offset = (params?.offset ?: '0').toInteger()

        List<Twgcb> twgcbList =  Twgcb.createCriteria().list(params) {
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            switch (filterType) {
                case PFilterType.QUICK:
                    projections {
                        property('id', 'id')
                        property('twgcbId', 'twgcbId')
                        property('cname', 'cname')
                        property('typeDesc', 'typeDesc')
                    }
                    break
                case PFilterType.FULL:
                    projections {
                        property('id', 'id')
                        property('twgcbId', 'twgcbId')
                        property('cname', 'cname')
                        property('type', 'type')
                        property('explanation', 'explanation')
                        property('instructions', 'instructions')
                        property('defaultValue', 'defaultValue')
                        property('remark', 'remark')
                        property('typeDesc', 'typeDesc')
                    }
                    break
                case PFilterType.VIEW:
                    projections {
                        property('id', 'id')
                        property('twgcbId', 'twgcbId')
                        property('cname', 'cname')
                        property('explanation', 'explanation')
                        property('instructions', 'instructions')
                        property('defaultValue', 'defaultValue')
                        property('remark', 'remark')
                        property('typeDesc', 'typeDesc')
                    }
                    break
                default:
                    projections {
                        property('twgcbId', 'twgcbId')
                        property('cname', 'cname')
                        sqlProjection("(select t.cdesc from bs_select t where t.type = 'twgcb_type' and t.code = this_.type ) typeDesc", ['typeDesc'], [StandardBasicTypes.STRING])
                    }
            }

            if (params?.twgcbId) {
                eq('twgcbId',params?.twgcbId)
            }

            if (params?.type) {
                int type = params?.type as int
                eq('type',type)
            }

            if (params?.id) {
                long id = params?.id as long
                eq('id',id)
            }

            if (params?.cname) {
                ilike('cname', "%${params?.cname}%")
            }

            order('twgcbId', 'asc')
        } as List<Twgcb>

        pFilterResult.domainList = twgcbList
        pFilterResult.totalCount = twgcbList.totalCount?:0

        return pFilterResult

    }
}

第一次優化

將顯示欄位邏輯拉出

  • 更改前
/**
* 查詢
* @param params
* @param filterType
* @return 查詢結果
*/
PFilterResult filter(
    GrailsParameterMap params,
    PFilterType filterType = PFilterType.DEFAULT
) {

    // 程式碼部份省略

    switch (filterType) {
      case PFilterType.QUICK:
          projections {
              property('id', 'id')
              property('twgcbId', 'twgcbId')
              property('cname', 'cname')
              property('typeDesc', 'typeDesc')
          }
          break
      case PFilterType.FULL:
          projections {
              property('id', 'id')
              property('twgcbId', 'twgcbId')
              property('cname', 'cname')
              property('type', 'type')
              property('explanation', 'explanation')
              property('instructions', 'instructions')
              property('defaultValue', 'defaultValue')
              property('remark', 'remark')
              property('typeDesc', 'typeDesc')
          }
          break
      case PFilterType.VIEW:
          projections {
              property('id', 'id')
              property('twgcbId', 'twgcbId')
              property('cname', 'cname')
              property('explanation', 'explanation')
              property('instructions', 'instructions')
              property('defaultValue', 'defaultValue')
              property('remark', 'remark')
              property('typeDesc', 'typeDesc')
          }
          break
      default:
          projections {
              property('twgcbId', 'twgcbId')
              property('cname', 'cname')
              sqlProjection("(select t.cdesc from bs_select t where t.type = 'twgcb_type' and t.code = this_.type ) typeDesc", ['typeDesc'], [StandardBasicTypes.STRING])
          }
    }

    // 程式碼部份省略

}
  • 更改後
/**
* 查詢
* @param params
* @param filterType
* @return 查詢結果
*/
PFilterResult filter(
    GrailsParameterMap params,
    PFilterType filterType = PFilterType.DEFAULT
) {

    // 程式碼部份省略

    switch (filterType) {
        case PFilterType.QUICK:
            Closure projection = (Closure) quickProjection.clone()
            projection.delegate = delegate
            projection()
            break
        case PFilterType.FULL:
            Closure projection = (Closure) fullProjection.clone()
            projection.delegate = delegate
            projection()
            break
        case PFilterType.VIEW:
            Closure projection = (Closure) viewProjection.clone()
            projection.delegate = delegate
            projection()
            break
        default:
            Closure projection = (Closure) defaultProjection.clone()
            projection.delegate = delegate
            projection()
    }

    // 程式碼部份省略

}

/**
  * 快速顯示欄位
  */
private Closure quickProjection =  {
}

/**
  * 完整顯示欄位
  */
private Closure fullProjection =  {
}

/**
  * 檢視單筆資料欄位
  */
private Closure viewProjection =  {
}

/**
  * 預設顯示欄位
  */
private Closure defaultProjection =  {
}

將查詢條件修飾

  • 更改前
/**
* 查詢
* @param params
* @param filterType
* @return 查詢結果
*/
PFilterResult filter(
    GrailsParameterMap params,
    PFilterType filterType = PFilterType.DEFAULT
) {

    // 程式碼部份省略

    if (params?.twgcbId) {
        eq('twgcbId',params?.twgcbId)
    }

    if (params?.type) {
        int type = params?.type as int
        eq('type',type)
    }

    if (params?.id) {
        long id = params?.id as long
        eq('id',id)
    }

    if (params?.cname) {
        ilike('cname', "%${params?.cname}%")
    }

    // 程式碼部份省略

}
  • 更改後
/**
* 查詢
* @param params
* @param filterType
* @return 查詢結果
*/
PFilterResult filter(
    GrailsParameterMap params,
    PFilterType filterType = PFilterType.DEFAULT
) {

    // 程式碼部份省略

    params.each { key, value ->
        if(value){
            switch (key) {
                case 'twgcbId':
                    eq('twgcbId', value)
                    break
                case 'type':
                    eq('type',value as int)
                    break
                case 'id':
                    eq('id', value as long)
                    break
                case 'cname':
                    ilike('cname', "%${value}%")
                    break
            }
        }
    }

    // 程式碼部份省略

}

完整程式碼如下

package work.pollochang.twgcb

import grails.gorm.transactions.Transactional
import grails.web.servlet.mvc.GrailsParameterMap
import org.hibernate.criterion.CriteriaSpecification
import org.hibernate.type.StandardBasicTypes
import work.pollochang.util.PFilterResult
import work.pollochang.util.PFilterType

/**
 * TWGCB 資料處理
 */
@Transactional
class TwGcbService {

    /**
     * 查詢
     * @param params
     * @param filterType
     * @return 查詢結果
     */
    PFilterResult filter(
            GrailsParameterMap params,
            PFilterType filterType = PFilterType.DEFAULT
    ) {
        PFilterResult pFilterResult = new PFilterResult()

        params.max = (params.max ?: '10').toInteger()
        params.offset = (params?.offset ?: '0').toInteger()

        List<Twgcb> twgcbList =  Twgcb.createCriteria().list(params) {
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            switch (filterType) {
                case PFilterType.QUICK:
                    Closure projection = (Closure) quickProjection.clone()
                    projection.delegate = delegate
                    projection()
                    break
                case PFilterType.FULL:
                    Closure projection = (Closure) fullProjection.clone()
                    projection.delegate = delegate
                    projection()
                    break
                case PFilterType.VIEW:
                    Closure projection = (Closure) viewProjection.clone()
                    projection.delegate = delegate
                    projection()
                    break
                default:
                    Closure projection = (Closure) defaultProjection.clone()
                    projection.delegate = delegate
                    projection()
            }

            params.each { key, value ->
                if(value){
                    switch (key) {
                        case 'twgcbId':
                            eq('twgcbId', value)
                            break
                        case 'type':
                            eq('type',value as int)
                            break
                        case 'id':
                            eq('id', value as long)
                            break
                        case 'cname':
                            ilike('cname', "%${value}%")
                            break
                    }
                }
            }

            order('twgcbId', 'asc')
        } as List<Twgcb>

        pFilterResult.domainList = twgcbList
        pFilterResult.totalCount = twgcbList.totalCount?:0

        return pFilterResult

    }

    /**
     * 快速顯示欄位
     */
    private Closure quickProjection =  {
        projections {
            property('id', 'id')
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            property('typeDesc', 'typeDesc')
        }
    }

    /**
     * 完整顯示欄位
     */
    private Closure fullProjection =  {
        projections {
            property('id', 'id')
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            property('type', 'type')
            property('explanation', 'explanation')
            property('instructions', 'instructions')
            property('defaultValue', 'defaultValue')
            property('remark', 'remark')
            property('typeDesc', 'typeDesc')
        }
    }

    /**
     * 檢視單筆資料欄位
     */
    private Closure viewProjection =  {
        projections {
            property('id', 'id')
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            property('explanation', 'explanation')
            property('instructions', 'instructions')
            property('defaultValue', 'defaultValue')
            property('remark', 'remark')
            property('typeDesc', 'typeDesc')
        }
    }

    /**
     * 預設顯示欄位
     */
    private Closure defaultProjection =  {
        projections {
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            sqlProjection("(select t.cdesc from bs_select t where t.type = 'twgcb_type' and t.code = this_.type ) typeDesc", ['typeDesc'], [StandardBasicTypes.STRING])
        }
    }
}

第二次優化

優化 projection 程式片段

/**
* 查詢
* @param params
* @param filterType
* @return 查詢結果
*/
PFilterResult filter(
    GrailsParameterMap params,
    PFilterType filterType = PFilterType.DEFAULT
) {

    // 程式碼部份省略

    switch (filterType) {
        case PFilterType.QUICK:
            Closure projection = (Closure) quickProjection.clone()
            projection.delegate = delegate
            projection()
            break
        case PFilterType.FULL:
            Closure projection = (Closure) fullProjection.clone()
            projection.delegate = delegate
            projection()
            break
        case PFilterType.VIEW:
            Closure projection = (Closure) viewProjection.clone()
            projection.delegate = delegate
            projection()
            break
        default:
            Closure projection = (Closure) defaultProjection.clone()
            projection.delegate = delegate
            projection()
    }

    // 程式碼部份省略

}

/**
* 查詢
* @param params
* @param filterType
* @return 查詢結果
*/
PFilterResult filter(
    GrailsParameterMap params,
    PFilterType filterType = PFilterType.DEFAULT
) {

    // 程式碼部份省略

    Closure projection = getProjectionClosure(filterType)
    if (projection) {
        projection.delegate = delegate
        projection()
    }

    // 程式碼部份省略

}


/**
  * 依 定義查詢種類 回傳查詢顯示欄位
  * @param filterType 定義查詢種類
  * @return 查詢顯示欄位
  */
private Closure getProjectionClosure(PFilterType filterType) {

    switch (filterType) {
        case PFilterType.QUICK:
            return quickProjection
        case PFilterType.FULL:
            return fullProjection
        case PFilterType.VIEW:
            return viewProjection
        default:
            return defaultProjection
    }

}

完整程式碼如下

package work.pollochang.twgcb

import grails.gorm.transactions.Transactional
import grails.web.servlet.mvc.GrailsParameterMap
import org.hibernate.criterion.CriteriaSpecification
import org.hibernate.type.StandardBasicTypes
import work.pollochang.util.PFilterResult
import work.pollochang.util.PFilterType

/**
 * TWGCB 資料處理
 */
@Transactional
class TwGcbService {

    /**
     * 查詢
     * @param params
     * @param filterType
     * @return 查詢結果
     */
    PFilterResult filter(
            GrailsParameterMap params,
            PFilterType filterType = PFilterType.DEFAULT
    ) {
        PFilterResult pFilterResult = new PFilterResult()

        params.max = (params.max ?: '10').toInteger()
        params.offset = (params?.offset ?: '0').toInteger()

        List<Twgcb> twgcbList =  Twgcb.createCriteria().list(params) {
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            Closure projection = getProjectionClosure(filterType)
            if (projection) {
                projection.delegate = delegate
                projection()
            }

            params.each { key, value ->
                if(value){
                    switch (key) {
                        case 'twgcbId':
                            eq('twgcbId', value)
                            break
                        case 'type':
                            eq('type',value as int)
                            break
                        case 'id':
                            eq('id', value as long)
                            break
                        case 'cname':
                            ilike('cname', "%${value}%")
                            break
                    }
                }
            }

            order('twgcbId', 'asc')
        } as List<Twgcb>

        pFilterResult.domainList = twgcbList
        pFilterResult.totalCount = twgcbList.totalCount?:0

        return pFilterResult

    }

    /**
     * 依 定義查詢種類 回傳查詢顯示欄位
     * @param filterType 定義查詢種類
     * @return 查詢顯示欄位
     */
    private Closure getProjectionClosure(PFilterType filterType) {
        switch (filterType) {
            case PFilterType.QUICK:
                return quickProjection
            case PFilterType.FULL:
                return fullProjection
            case PFilterType.VIEW:
                return viewProjection
            default:
                return defaultProjection
        }
    }

    /**
     * 快速顯示欄位
     */
    private Closure quickProjection =  {
        projections {
            property('id', 'id')
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            property('typeDesc', 'typeDesc')
        }
    }

    /**
     * 完整顯示欄位
     */
    private Closure fullProjection =  {
        projections {
            property('id', 'id')
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            property('type', 'type')
            property('explanation', 'explanation')
            property('instructions', 'instructions')
            property('defaultValue', 'defaultValue')
            property('remark', 'remark')
            property('typeDesc', 'typeDesc')
        }
    }

    /**
     * 檢視單筆資料欄位
     */
    private Closure viewProjection =  {
        projections {
            property('id', 'id')
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            property('explanation', 'explanation')
            property('instructions', 'instructions')
            property('defaultValue', 'defaultValue')
            property('remark', 'remark')
            property('typeDesc', 'typeDesc')
        }
    }

    /**
     * 預設顯示欄位
     */
    private Closure defaultProjection =  {
        projections {
            property('twgcbId', 'twgcbId')
            property('cname', 'cname')
            sqlProjection("(select t.cdesc from bs_select t where t.type = 'twgcb_type' and t.code = this_.type ) typeDesc", ['typeDesc'], [StandardBasicTypes.STRING])
        }
    }

}