最佳实践 #

项目结构 #

标准 Gem 结构 #

text
mygem/
├── bin/                    # 可执行脚本
│   ├── console            # 开发控制台
│   └── setup              # 安装脚本
├── exe/                    # 可执行文件
│   └── mygem              # 命令行工具
├── lib/                    # 源代码
│   ├── mygem/
│   │   ├── core/          # 核心功能
│   │   ├── utils/         # 工具类
│   │   ├── errors.rb      # 错误定义
│   │   └── version.rb     # 版本信息
│   └── mygem.rb           # 入口文件
├── spec/                   # 测试文件
│   ├── spec_helper.rb
│   ├── mygem_spec.rb
│   └── mygem/
│       └── core_spec.rb
├── docs/                   # 文档
│   └── getting_started.md
├── .github/                # GitHub 配置
│   ├── workflows/
│   │   └── ci.yml
│   ├── ISSUE_TEMPLATE.md
│   └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .rspec
├── .rubocop.yml
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── CHANGELOG.md
└── mygem.gemspec

文件命名规范 #

ruby
# 文件名使用 snake_case
# lib/my_gem.rb
# lib/my_gem/my_class.rb

# 类名使用 CamelCase
module MyGem
  class MyClass
  end
end

版本管理 #

语义化版本 #

text
MAJOR.MINOR.PATCH

MAJOR: 不兼容的 API 变更
MINOR: 向后兼容的功能新增
PATCH: 向后兼容的 bug 修复

预发布版本:
1.0.0.alpha.1
1.0.0.beta.1
1.0.0.rc.1

版本文件 #

ruby
# lib/mygem/version.rb
# frozen_string_literal: true

module Mygem
  VERSION = "1.0.0"
end

变更日志 #

markdown
# CHANGELOG.md

## [1.0.0] - 2024-01-01

### Added
- New feature X
- New feature Y

### Changed
- Changed behavior of Z

### Fixed
- Bug fix for A

### Removed
- Deprecated feature B

## [0.9.0] - 2023-12-01

### Added
- Initial features

依赖管理 #

依赖声明原则 #

ruby
# mygem.gemspec

# 运行时依赖:使用悲观约束
spec.add_dependency "nokogiri", "~> 1.14"
spec.add_dependency "json", ">= 2.6", "< 3.0"

# 开发依赖:宽松版本
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency "rubocop", "~> 1.0"

# 可选依赖
spec.add_optional_dependency "redis", "~> 5.0"

最小依赖原则 #

ruby
# 不推荐:依赖过多
spec.add_dependency "activesupport"
spec.add_dependency "json"
spec.add_dependency "nokogiri"

# 推荐:只依赖必要的
spec.add_dependency "json"  # 只依赖真正需要的

避免版本冲突 #

ruby
# 检查依赖冲突
# bundle viz

# 使用宽松约束
spec.add_dependency "rack", ">= 2.0", "< 4.0"

# 避免精确版本
# 不推荐
spec.add_dependency "rails", "7.0.4.1"

# 推荐
spec.add_dependency "rails", "~> 7.0"

代码质量 #

代码风格 #

yaml
# .rubocop.yml
AllCops:
  TargetRubyVersion: 2.7
  NewCops: enable

Style/StringLiterals:
  EnforcedStyle: double_quotes

Style/Documentation:
  Enabled: true

Metrics/MethodLength:
  Max: 20

Metrics/ClassLength:
  Max: 150

代码组织 #

ruby
# lib/mygem.rb
# frozen_string_literal: true

require_relative "mygem/version"
require_relative "mygem/errors"
require_relative "mygem/configuration"
require_relative "mygem/client"

module Mygem
  class << self
    attr_accessor :configuration

    def configure
      self.configuration ||= Configuration.new
      yield(configuration) if block_given?
      configuration
    end

    def reset!
      self.configuration = nil
    end

    def client
      @client ||= Client.new(configuration)
    end
  end
end

错误处理 #

ruby
# lib/mygem/errors.rb
# frozen_string_literal: true

module Mygem
  class Error < StandardError; end
  class ConfigurationError < Error; end
  class ConnectionError < Error; end
  class TimeoutError < Error; end
  class AuthenticationError < Error; end
  class ValidationError < Error; end
  class NotFoundError < Error; end
  class RateLimitError < Error; end
end

日志记录 #

ruby
# lib/mygem/logger.rb
# frozen_string_literal: true

require "logger"

module Mygem
  class << self
    attr_accessor :logger

    def logger
      @logger ||= Logger.new($stdout).tap do |log|
        log.level = Logger::INFO
        log.formatter = proc do |severity, datetime, _progname, msg|
          "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity}: #{msg}\n"
        end
      end
    end
  end
end

测试策略 #

测试组织 #

text
spec/
├── spec_helper.rb          # 测试配置
├── mygem_spec.rb           # 主模块测试
├── mygem/
│   ├── client_spec.rb      # 客户端测试
│   ├── request_spec.rb     # 请求测试
│   └── response_spec.rb    # 响应测试
├── fixtures/               # 测试数据
│   └── users.json
├── support/                # 测试辅助
│   └── helpers.rb
└── integration/            # 集成测试
    └── api_spec.rb

测试最佳实践 #

ruby
# spec/mygem/client_spec.rb
# frozen_string_literal: true

RSpec.describe Mygem::Client do
  let(:client) { described_class.new(api_key: "test_key") }

  before do
    Mygem.reset!
    Mygem.configure do |config|
      config.base_url = "https://api.example.com"
    end
  end

  after do
    Mygem.reset!
  end

  describe "#initialize" do
    it "accepts configuration options" do
      client = described_class.new(
        api_key: "custom_key",
        timeout: 60
      )
      
      expect(client.api_key).to eq("custom_key")
      expect(client.timeout).to eq(60)
    end

    it "uses default configuration" do
      Mygem.configure do |config|
        config.api_key = "default_key"
      end
      
      client = described_class.new
      expect(client.api_key).to eq("default_key")
    end
  end

  describe "#get" do
    context "when successful" do
      before do
        stub_request(:get, "https://api.example.com/users")
          .to_return(
            status: 200,
            body: '{"users": []}',
            headers: { "Content-Type" => "application/json" }
          )
      end

      it "returns successful response" do
        response = client.get("/users")
        
        expect(response).to be_success
        expect(response.body).to eq({ "users" => [] })
      end
    end

    context "when rate limited" do
      before do
        stub_request(:get, "https://api.example.com/users")
          .to_return(status: 429, body: '{"error": "Rate limited"}')
      end

      it "raises rate limit error" do
        expect { client.get("/users") }
          .to raise_error(Mygem::RateLimitError)
      end
    end
  end
end

测试覆盖率 #

ruby
# spec/spec_helper.rb
# frozen_string_literal: true

require "simplecov"
SimpleCov.start do
  add_filter "/spec/"
  add_filter "/vendor/"
  
  add_group "Core", "lib/mygem/core"
  add_group "Utils", "lib/mygem/utils"
end

require "bundler/setup"
require "mygem"

文档编写 #

README 结构 #

markdown
# Mygem

简短描述

## Installation

安装说明

## Quick Start

快速开始示例

## Usage

详细用法

## Configuration

配置说明

## API Reference

API 文档

## Examples

示例代码

## Development

开发指南

## Contributing

贡献指南

## License

许可证

代码注释 #

ruby
# lib/mygem/client.rb
module Mygem
  # Client for interacting with the API
  #
  # @example Creating a client
  #   client = Mygem::Client.new(api_key: 'your_key')
  #   response = client.get('/users')
  #
  class Client
    # Initialize a new client
    #
    # @param options [Hash] Configuration options
    # @option options [String] :api_key API key
    # @option options [Integer] :timeout Timeout in seconds
    #
    # @return [Client] A new client instance
    #
    # @example
    #   client = Mygem::Client.new(
    #     api_key: 'secret',
    #     timeout: 60
    #   )
    #
    def initialize(options = {})
      @api_key = options[:api_key]
      @timeout = options[:timeout] || 30
    end

    # Make a GET request
    #
    # @param path [String] API endpoint path
    # @param params [Hash] Query parameters
    #
    # @return [Response] The response object
    #
    # @raise [ConnectionError] When connection fails
    # @raise [TimeoutError] When request times out
    #
    # @example
    #   response = client.get('/users', page: 1)
    #   puts response.body
    #
    def get(path, params = {})
      # ...
    end
  end
end

安全实践 #

输入验证 #

ruby
# lib/mygem/validator.rb
module Mygem
  module Validator
    def self.validate_api_key(api_key)
      raise ValidationError, "API key is required" if api_key.nil? || api_key.empty?
      raise ValidationError, "Invalid API key format" unless api_key.match?(/\A[a-zA-Z0-9]{32}\z/)
      api_key
    end

    def self.validate_timeout(timeout)
      raise ValidationError, "Timeout must be positive" if timeout && timeout <= 0
      raise ValidationError, "Timeout must be an integer" unless timeout.is_a?(Integer)
      timeout
    end
  end
end

敏感信息处理 #

ruby
# lib/mygem/client.rb
module Mygem
  class Client
    def initialize(options = {})
      @api_key = options[:api_key]
    end

    # 不在日志中暴露敏感信息
    def inspect
      "#<Mygem::Client api_key=[FILTERED]>"
    end

    private

    def headers
      {
        "Authorization" => "Bearer #{@api_key}",
        "Content-Type" => "application/json"
      }
    end
  end
end

安全依赖 #

bash
# 检查安全漏洞
bundle audit

# 更新漏洞数据库
bundle audit update

# 检查已知漏洞
bundle audit check

性能优化 #

延迟加载 #

ruby
# lib/mygem.rb
module Mygem
  autoload :Client, "mygem/client"
  autoload :Request, "mygem/request"
  autoload :Response, "mygem/response"
end

缓存机制 #

ruby
# lib/mygem/cache.rb
module Mygem
  class Cache
    def initialize(ttl: 300)
      @cache = {}
      @ttl = ttl
    end

    def fetch(key)
      return @cache[key][:value] if cached?(key)
      
      @cache[key] = {
        value: yield,
        expires_at: Time.now + @ttl
      }
      @cache[key][:value]
    end

    def clear
      @cache.clear
    end

    private

    def cached?(key)
      @cache.key?(key) && @cache[key][:expires_at] > Time.now
    end
  end
end

连接复用 #

ruby
# lib/mygem/connection.rb
require "net/http"

module Mygem
  class Connection
    def initialize(base_url, timeout)
      @uri = URI(base_url)
      @http = Net::HTTP.new(@uri.host, @uri.port)
      @http.use_ssl = @uri.scheme == "https"
      @http.read_timeout = timeout
      @http.open_timeout = timeout
    end

    def request(request)
      @http.request(request)
    end
  end
end

发布流程 #

发布前检查清单 #

markdown
- [ ] 更新版本号
- [ ] 更新 CHANGELOG.md
- [ ] 运行所有测试
- [ ] 运行代码检查
- [ ] 更新文档
- [ ] 检查依赖安全
- [ ] 构建并测试 Gem
- [ ] 创建 Git 标签
- [ ] 推送到 RubyGems

自动化发布 #

yaml
# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true
          
      - name: Run tests
        run: bundle exec rspec
        
      - name: Run linter
        run: bundle exec rubocop
        
      - name: Build gem
        run: gem build mygem.gemspec
        
      - name: Push to RubyGems
        run: |
          mkdir -p $HOME/.gem
          touch $HOME/.gem/credentials
          chmod 0600 $HOME/.gem/credentials
          printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
          gem push mygem-*.gem
        env:
          RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}

维护指南 #

问题管理 #

markdown
# .github/ISSUE_TEMPLATE.md

## Description
描述问题

## Steps to Reproduce
复现步骤

## Expected Behavior
期望行为

## Actual Behavior
实际行为

## Environment
- Ruby version:
- Gem version:
- OS:

Pull Request 模板 #

markdown
# .github/PULL_REQUEST_TEMPLATE.md

## Description
描述变更

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Checklist
- [ ] Tests added/updated
- [ ] Documentation updated
- [ ] CHANGELOG updated
- [ ] No breaking changes

版本支持 #

markdown
## Supported Ruby Versions

- Ruby 2.7
- Ruby 3.0
- Ruby 3.1
- Ruby 3.2

## Supported Platforms

- Linux
- macOS
- Windows

常见问题解决 #

依赖冲突 #

bash
# 查看依赖树
bundle viz

# 更新特定 Gem
bundle update mygem --conservative

# 强制解析
bundle update --full-index

安装失败 #

bash
# 清理缓存
bundle clean --force

# 重新安装
rm -rf vendor/bundle Gemfile.lock
bundle install

权限问题 #

bash
# 安装到用户目录
gem install mygem --user-install

# 或使用 Bundler
bundle install --path vendor/bundle

总结 #

遵循这些最佳实践可以帮助你:

  1. 提高代码质量:清晰的代码结构和风格
  2. 增强可维护性:完善的文档和测试
  3. 保证安全性:输入验证和依赖审计
  4. 优化性能:延迟加载和缓存机制
  5. 简化发布流程:自动化 CI/CD

持续学习和改进是成为 RubyGem 开发专家的关键!

最后更新:2026-03-28