استفاده از linter در golang

در اکثر مواقع استفاده از لینترها در هر زبانی میتونه از جنبه‌های مختلف کمک زیادی برای بهبود کدها انجام بدن اما هنوز هم دولوپرها بخوبی باهاشون آشتی نکردن.

لینترها با هزینه پایین، میتونن کدها رو از جنبه‌های مختلفی بررسی کنن و خب لینترهای گولنگی هم از این قاعده مستثنی نیستند.

لینترهای گولنگی می‌تونن بهمون کمک کنن:

  • فیلد‌های یک struct طوری بچینیم که با ساختار memory سازگارتر باشه و مموری فوت‌پرینت رو کمتر کنیم
  • هر زمان که فراموش کردیم یه خطا رو به‌درستی لاگ یا wrap کنیم بهمون اخطار بدن
  • کدهای duplicate رو تشخیص بدن
  • اجازه ندن هر dependency در هرجایی ایمپورت بشه
  • از هاردکدها و مجیک نامبرها جلوگیری میکنن
  • برای ifهای بیش از اندازه توو در توو نمره منفی میذارن
  • و حتی میشه اجازه ندیم بعضی مواقع اسلایسی بدون ظرفیت مناسب تعریف بشه
  • و…

اما احساس میکنم یکی از مهمترین قابلیت‌های لینترها ایجاد یک چارچوب مشخص برای نوشتن کدها در هنگام توسعه تیمی و در فرآیندهای مختلفی مثل CI هست. وقتی تیمی کار می‌کنید بیش از هر زمان دیگه‌ای نسبت به رعایت یک سری قوانین مشخص احساس نیاز میشه که خب میتونه با استفاده از لینترها دقیق‌تر پیاده بشه.

خب golangci-lint یکی از ابزارهاییه که به سادگی به ما اجازه میده مجموعه‌ای از لینترهای محبوب مثل revive و… رو ترکیب کنیم و یه فایل چارچوب کدنویسی متناسب با پروژه و تیممون درست کنیم. فقط کافیه golangci-lint رو نصب و یه فایل yml یا json که شامل ruleها میشه براش بنویسیم.

یه نمونه فایل کانفیگ ساده رو اینجا میذارم، شاید دوست داشتین تست کنید:

linters-settings:
  asasalint:  # will prevent any function for having "...any" variadic parameter except log funcs
    exclude:
      - \.Debugf
      - \.Infof
      - \.Warnf
      - \.Errorf
      - \.Fatalf
    ignore-test: true

  errcheck:
    check-type-assertions: true

  gocyclo:
    min-complexity: 5

  dupl:
    threshold: 100

  goconst:
    min-len: 2
    min-occurrences: 3
    ignore-tests: true
    
  gocritic:
    enabled-tags:
      - diagnostic
      - experimental
      - opinionated
      - performance
      - style

  gomnd:
    checks:
      - argument
      - case
      - condition
      - operation
      - return
      - assign
    ignored-numbers:
      - '0666'  # will ignore hard coded numbers which are permission codes, but a better approach is using octal number e.g: 0o666 and remove this section
      - '0755'
    ignored-functions:
      - '^math\.'  # will ignore hard coded numbers which are used alongside math, e.g: math.rand() * 2

  govet:
    check-shadowing: true
    enable:
      - fieldalignment
      - nilfunc
      - nilness

  revive:
    severity: error
    enable-all-rules: true
    confidence: 0.8
    rules:
      - name: unused-parameter
        severity: warning
        disabled: false
        # arguments:
        #  - allowRegex: "."  # use regex to allow an unused-parameter when you need, e.g: (tx *gorm.DB) in gorm hooks
      - name: unused-receiver
        severity: warning
        disabled: false
        # arguments:
        #  - allowRegex: "."  # use regex to allow an unused-receiver when you need
      - name: line-length-limit  # will check for line length but you have to enable it
        severity: warning 
        disabled: true
        arguments: [80]  # pass a desired number for each line length restriction
      - name: unchecked-type-assertion
        severity: warning
        disabled: true
      - name: add-constant
        severity: warning
        disabled: false
        arguments:
          - maxLitCount: "5"
            allowStrs: '""'
            allowInts: "0,1,2,3,4"
      - name: cognitive-complexity  # will check for code complexity and every if, else, ||, & and ! charactars will add to complexity score
        severity: warning
        disabled: true
        arguments: [10]  # pass a desired number for complexity score, smaller numbers means more restrictions for using if, else, ||, & and ! chars

  nolintlint:
    require-explanation: true
    require-specific: true
    
  depguard: # will check dependencies and imported packages with defined rules
    rules:
      main:
        files:
          - "!**/*_a _file.go"
        allow:
          - $gostd
          - github.com/example
        deny:
          - pkg: "github.com/pkg/example"
            desc: should be replaced by blah blah package

linters:
  disable-all: true
  enable:
    - asasalint
    - cyclop
    # - depguard # if enabled, will check dependencies and imported packages with above rules
    - dupl
    - errcheck
    # - errorlint # if enabled, will check for wrapping errors and error type assertions without ,ok idioms
    - exhaustive
    - goconst
    - gocritic
    # - godox # if enabled, will check codes for finding "todos", "bug" and "fixme" pharses
    - gocyclo
    - gomnd
    - gosimple
    - gosec
    - govet  
    - misspell
    - musttag
    - perfsprint
    - prealloc
    - predeclared
    - usestdlibvars
    - whitespace
    - wsl
    - revive 
    - bodyclose
    - exportloopref
    - ineffassign
    - nolintlint
    - stylecheck
    - unconvert

run:
  issues-exit-code: 1

issues:
  exclude-rules:
    - path: _test\.go 
      linters:
        - gocyclo
        - gosec
        - dupl