最佳实践 #
项目结构 #
标准 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
总结 #
遵循这些最佳实践可以帮助你:
- 提高代码质量:清晰的代码结构和风格
- 增强可维护性:完善的文档和测试
- 保证安全性:输入验证和依赖审计
- 优化性能:延迟加载和缓存机制
- 简化发布流程:自动化 CI/CD
持续学习和改进是成为 RubyGem 开发专家的关键!
最后更新:2026-03-28