mcpField
S
separation-of-concerns

separation-of-concerns

github.com/yanko-belov开发1364 次安装41 周前更新
代码仓库

关于 separation-of-concerns

面向软件开发者的关注点分离编码指南,主张将数据获取、业务逻辑、界面呈现和层间组合拆分。指南通过检测信号、示例和分层建议,说明如何提升代码的可测试性、复用性与可维护性。

主要功能

  1. 01说明数据、逻辑与呈现分层
  2. 02列出混合关注点的识别信号
  3. 03提供分层代码模式
  4. 04说明测试与复用的收益

适用场景

  1. 01重构同时获取和展示数据的组件
  2. 02拆分控制器中的数据库查询与业务逻辑
  3. 03提高独立测试和复用能力

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/yanko-belov/code-craft --skill separation-of-concerns

安装后在 Claude Code 中输入/separation-of-concerns确认已加载。

详细介绍

职责分离

概述

每段代码都应该只做一件事。数据、逻辑和表现应彼此分离。

职责混杂会导致代码无法测试、无法复用且难以维护。职责分离有助于测试、复用和保持清晰。

适用场景

  • 组件同时获取、转换和显示数据
  • 业务逻辑与 UI 代码混杂
  • 控制器中包含数据库查询
  • 难以单独测试某段代码

铁律

NEVER mix data fetching, business logic, and presentation in one place.

没有例外:

  • 不能因为“这是个小组件”
  • 不能因为“这样更简单”
  • 不能因为“只用一次”
  • 不能因为“它能运行”

检测:职责混杂的迹象

如果一个文件同时负责获取、转换和显示数据,请立即停止:

// ❌ VIOLATION: Component does everything
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Data fetching
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        // Business logic / transformation
        const fullName = `${data.firstName} ${data.lastName}`;
        const memberSince = new Date(data.createdAt).toLocaleDateString();
        const isVIP = data.orderCount > 100;
        
        setUser({ ...data, fullName, memberSince, isVIP });
        setLoading(false);
      });
  }, [userId]);
  
  // Presentation
  if (loading) return <div>Loading...</div>;
  
  return (
    <div className="user-profile">
      <h1>{user.fullName}</h1>
      {user.isVIP && <span className="vip-badge">VIP</span>}
      <p>Member since: {user.memberSince}</p>
    </div>
  );
}

问题:

  • 无法在不获取数据的情况下测试格式化
  • 无法复用获取数据的逻辑
  • 无法复用表现层
  • 一个组件承担了 3 项工作

正确模式:分层

// ✅ CORRECT: Separated concerns

// 1. Data fetching (hook)
// hooks/useUser.ts
function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  
  useEffect(() => {
    setLoading(true);
    fetch(`/api/users/${userId}`)
      .then(res => {
        if (!res.ok) throw new Error('Failed to fetch');
        return res.json();
      })
      .then(setUser)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);
  
  return { user, loading, error };
}

// 2. Business logic (pure functions)
// utils/userFormatters.ts
interface FormattedUser {
  fullName: string;
  memberSince: string;
  isVIP: boolean;
}

function formatUser(user: User): FormattedUser {
  return {
    fullName: `${user.firstName} ${user.lastName}`,
    memberSince: new Date(user.createdAt).toLocaleDateString(),
    isVIP: user.orderCount > 100,
  };
}

// 3. Presentation (dumb component)
// components/UserCard.tsx
interface UserCardProps {
  fullName: string;
  memberSince: string;
  isVIP: boolean;
}

function UserCard({ fullName, memberSince, isVIP }: UserCardProps) {
  return (
    <div className="user-profile">
      <h1>{fullName}</h1>
      {isVIP && <span className="vip-badge">VIP</span>}
      <p>Member since: {memberSince}</p>
    </div>
  );
}

// 4. Composition (container component)
// pages/UserProfile.tsx
function UserProfile({ userId }) {
  const { user, loading, error } = useUser(userId);
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return <NotFound />;
  
  const formatted = formatUser(user);
  
  return <UserCard {...formatted} />;
}

职责分离的好处

混杂分离
无法测试格式化可单独测试 formatUser()
无法复用数据获取逻辑useUser() 可在任何地方复用
无法复用 UIUserCard 可配合任何数据复用
1 个复杂组件4 个简单部分

各层说明

1. 数据层(Hooks、服务)
  • 获取数据
  • API 调用
  • 状态管理
  • 不包含业务逻辑
2. 逻辑层(纯函数)
  • 数据转换
  • 计算
  • 验证
  • 业务规则
3. 表现层(组件)
  • 渲染 UI
  • 样式
  • 用户交互
  • 不获取数据
4. 组合层(容器)
  • 连接各层
  • 处理加载和错误状态
  • 向下传递数据

抵御压力的准则

1. “这是个小组件”

压力:“对于简单场景,分离职责是小题大做。”

**回应:**小组件会变大。从一开始就保持整洁。

**行动:**即使是小组件也要分离职责。这样做成本很低。

2. “这样更简单”

压力:“把所有内容放在一处更容易理解。”

**回应:**职责混杂看似简单,实际上难以测试、调试和修改。

**行动:**从长远来看,职责分离更简单。

3. “只用一次”

压力:“这个组件很独特,不会复用。”

**回应:**即使是独特的组件,可测试性也很重要。

**行动:**为了可测试性而分离,而不只是为了复用。

危险信号——立即停止并重新考虑

  • useEffect 中同时包含获取数据、转换数据和 setState
  • 渲染函数中包含业务逻辑
  • 路由处理程序中包含数据库查询
  • 工具函数中包含 API 调用
  • 超过 100 行的组件

这些情况都意味着:分离职责。

快速参考

职责所属位置
API 调用Hooks / 服务
数据转换纯函数
业务规则纯函数
UI 渲染表现组件
连接各部分容器组件

常见的自我开脱(均不成立)

借口事实
“小组件”小组件会变大。现在就分离。
“放在一起更简单”分离后更容易测试和修改。
“只用一次”可测试性很重要。
“它能运行”能运行 ≠ 易于维护。
“过度设计”这只是正常的工程实践。

归根结底

在 Hooks 中获取数据。在纯函数中处理逻辑。在组件中编写 UI。

职责分离有助于测试、复用和维护。组件应该只负责获取数据、转换数据或显示数据之一——绝不能三者兼顾。

评论

支持 Markdown