Agent Skills: coding-practices

>

UncategorizedID: rstacruz/agentic-toolkit/coding-practices

Install this agent skill to your local

pnpm dlx add-skill https://github.com/rstacruz/agentic-toolkit/tree/HEAD/skill/atk/coding-practices

Skill Files

Browse the full folder contents for coding-practices.

Download Skill

Loading file tree…

skill/atk/coding-practices/SKILL.md

Skill Metadata

Name
coding-practices
Description
>

CP1: Functional core, imperative shell

Separate pure logic (core) from side effects (shell).

Functional core:

  • Pure functions only
  • No I/O, DB calls, external state
  • Testable in isolation
  • Operates on given data only

Imperative shell:

  • Handles all side effects
  • DB calls, network, file I/O
  • Calls core for business logic
  • Thin layer around core

Further reading: https://testing.googleblog.com/2025/10/simplify-your-code-functional-core.html

AVOID: mixed

function sendExpiryEmails() {
  for (const user of db.getUsers()) {
    if (user.subscriptionEndDate <= Date.now() && !user.isFreeTrial) {
      email.send(user.email, `Expired ${user.name}`);
    }
  }
}

BETTER: separated

// Core
function getExpiredUsers(users, cutoff) {
  return users.filter((u) => u.subscriptionEndDate <= cutoff && !u.isFreeTrial);
}

function generateEmails(users) {
  return users.map((u) => [u.email, `Expired ${u.name}`]);
}

// Shell
const expiredUsers = getExpiredUsers(db.getUsers(), Date.now());
if (expiredUsers.length > 0) {
  email.bulkSend(generateEmails(expiredUsers));
}

CP2. Operational vs unexpected errors

Distinguish error types for appropriate handling.

Operational:

  • Expected failures during normal operation
  • User issues, validation failures, network timeouts
  • Handle gracefully, log, continue
  • Return error values not exceptions

Unexpected:

  • Programming bugs, system failures
  • Memory leaks, null refs, logic errors
  • Throw exceptions, crash/restart
  • Should not occur in production

Further reading: https://github.com/goldbergyoni/nodebestpractices/blob/master/sections/errorhandling/operationalvsprogrammererror.md

AVOID: treat all errors same

function publishArticle(articleId) {
  const article = db.getArticle(articleId);
  if (!article) throw new Error("Article not found"); // operational as exception
  if (article.wordCount < 300) throw new Error("Too short");
  article.status = "published";
  db.save(article);
  return article;
}

try {
  publishArticle(456);
} catch (err) {
  logger.error(err);
  process.exit(1); // crashing for operational
}

BETTER: different handling per type

// Returns error objects for operational
function validateArticleForPublishing(article) {
  if (!article) return { ok: false, error: "Article not found" };
  if (article.wordCount < 300) return { ok: false, error: "Article too short" };
  if (!article.title) return { ok: false, error: "Missing title" };
  return { ok: true };
}

function markAsPublished(article) {
  return { ...article, status: "published", publishedAt: Date.now() };
}

function publish(articleId) {
  const article = db.getArticle(articleId);
  const validation = validateArticleForPublishing(article);

  if (!validation.ok) {
    logger.info(`Cannot publish article 456`, { error: validation.error }); // log and continue
    return res.status(400).json({ error: validation.error });
  }

  const published = markAsPublished(article);
  db.save(published);
}

CP3: Result-oriented interface pattern

Use result objects for complex operations with expected failures.

Result objects:

  • Success: { ok: true, result, data }
  • Failure: { ok: false, result, error }
  • Names flexible per project
  • Return parseable result code when possible

AVOID: exceptions for expected errors

function parseJson(input: string) {
  if (!input.trim()) throw new Error("Empty input");
  try {
    return JSON.parse(input);
  } catch {
    throw new Error("Invalid JSON");
  }
}

BETTER: result objects

function parseJson(input: string) {
  if (!input.trim()) return { ok: false, result: "EMPTY_INPUT" };
  try {
    return { ok: true, result: "OK", data: JSON.parse(input) };
  } catch {
    return { ok: false, result: "INVALID_JSON" };
  }
}

Further reading: Railway-oriented programming (F#, Rust's Result<T, E>)

CP4: Presentational vs container components

Separate React components by responsibility.

Presentational:

  • Concerned with appearance
  • Receive data/callbacks via props
  • No API calls or state management
  • Focus on markup/styling
  • Highly reusable

Container:

  • Concerned with behaviour
  • Manage state and side effects
  • Handle data fetching, business logic
  • Pass data/callbacks to presentational

AVOID: mixed

function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchUser().then(setUser).catch(setError)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>No user found</div>;

  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} className="avatar" />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <button onClick={() => updateUserStatus(user.id, "active")}>
        Activate
      </button>
    </div>
  );
}

BETTER: separated (View presentational, Profile container, useUser hook)

function UserProfileView({ user, onActivate }: UserViewProps) {
  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} className="avatar" />
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <button onClick={onActivate}>Activate</button>
    </div>
  );
}

function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetchUser(userId).then(setUser).catch(setError)
      .finally(() => setLoading(false));
  }, [userId]);

  const activate = useCallback(() => {
    if (user) updateUserStatus(user.id, "active");
  }, [user]);

  return { user, loading, error, activate };
}

function UserProfile({ userId }: { userId: string }) {
  const { user, loading, error, activate } = useUser(userId);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>No user found</div>;

  return <UserProfileView user={user} onActivate={activate} />;
}

Further reading: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

CP5. Log context builder pattern

When logging gets redundant, build log context incrementally. Only use if reduces lines.

AVOID: repetitive

function acceptLatestEvent(session) {
  const user = session.getUser();
  if (!user) log.info("No user found", { session_id: session.id });

  const event = user.getLatestEvent();
  if (!event)
    log.info("No event found", {
      session_id: session.id, user_id: user.id, profile_id: user.profileId,
    });

  logContext.event_id = event.id;
  event.accept();
  log("Event accepted", {
    session_id: session.id, user_id: user.id, profile_id: user.profileId, event_id: event.id,
  });
}

BETTER: construct context as you go

function acceptLatestEvent(session) {
  const logContext = {
    session_id: session.id, user_id: null, profile_id: null, event_id: null,
  };

  const user = getUser();
  if (!user) log.info("No user found", logContext);

  logContext.user_id = user.id;
  logContext.profile_id = user.profileId;
  const event = user.getLatestEvent();
  if (!event) log.info("No event found", logContext);

  logContext.event_id = event.id;
  event.accept();
  log("Event accepted", logContext);
}