{"id":17086544,"url":"https://github.com/arl/sockdrawer","last_synced_at":"2025-03-23T14:41:15.845Z","repository":{"id":57712735,"uuid":"514407173","full_name":"arl/sockdrawer","owner":"arl","description":"The sockdrawer tool helps you reorganize a complex Go package into several simpler ones.","archived":false,"fork":false,"pushed_at":"2022-07-17T06:05:45.000Z","size":25,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-28T20:44:33.328Z","etag":null,"topics":["go","package","refactor"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-15T21:16:32.000Z","updated_at":"2024-01-30T10:01:38.000Z","dependencies_parsed_at":"2022-09-26T21:30:47.807Z","dependency_job_id":null,"html_url":"https://github.com/arl/sockdrawer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arl%2Fsockdrawer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arl%2Fsockdrawer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arl%2Fsockdrawer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arl%2Fsockdrawer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arl","download_url":"https://codeload.github.com/arl/sockdrawer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245119575,"owners_count":20563762,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","package","refactor"],"created_at":"2024-10-14T13:28:54.826Z","updated_at":"2025-03-23T14:41:15.803Z","avatar_url":"https://github.com/arl.png","language":"Go","readme":"# sockdrawer\n\nThe sockdrawer command is an analysis and visualization tool to help\nyou reorganize a complex Go package into several simpler ones.\n\nInstall with:\n\n```\ngo install github.com/arl/sockdrawer@latest\n```\n\n## Disclaimer\n\nThe code is in this repository is based on the `sockdrawer` program, an original\nwork by [Alan Donovan](github.com/adonovan), which is accessible at\nhttps://codereview.appspot.com/186270043/. \n\nAs such it's covered by the original license and copyrighted to the Go\nAuthors.\n\nThere are some rough edges, mostly due to the fact that sockdrawer has been\ndeveloped way before the apparition of modules.\n\nThe only differences with the original repository have been made so the project\ncan build.\n\nNo other modificaitons are planned for now.\n\n## Overview\n\nsockdrawer operates on three kinds of graphs at different levels of\nabstraction.  The lowest level is the NODE GRAPH.  A node is a\npackage-level declaration of a named entity (func, var, const or type).\n\nAn entire constant declaration is treated as a single node, even if it\ncontains multiple \"specs\" each defining multiple names, since constants\nso grouped are typically closely related; an important special case is\nan enumerated set data type.  Also, we treat each \"spec\" of a var or\ntype declaration as a single node.\n\n```go\nfunc f()                        // a func node\nconst ( a, b = 0, 1; c = 0 )    // a single const node\nvar (\n    a, b = 0, 1                 // a single var node\n    c = 0                       // another var node\n)\ntype ( x int; y int )           // a single type node\n```\n\nEach reference to a package-level entity E forms an edge in the node\ngraph, from the node in which it appears to the node E.  For example:\n\n```go\nvar x int\nvar y = x                   // edge y -\u003e x\nfunc f() int { return y }   // edge f -\u003e y\n```\n\nEach method declaration depends on its receiver named type; in addition\nwe add an edge from each receiver type to its methods:\n\n```go\ntype T int      // edge T -\u003e T.f\nfunc (T) f()    // edge T.f -\u003e T\n```\nto ensure that a type and its methods stay together.\n\nThe node graph is highly cyclic, and obviously all nodes in a cycle must\nbelong to the same package for the package import graph to remain\nacyclic.\n\nSo, we compute the second graph, the SCNODE GRAPH.  In essence, the\nscnode graph is the graph of strongly connected components (SCCs) of the\n(ordinary) node graph.  By construction, the scnode graph is acyclic.\n\nWe optionally perform an optimization at this point, which is to fuse\nsingle-predecessor scnodes with their sole predecessor, as this tends to\nreduce clutter in big graphs.  This means that the scnodes are no longer\ntrue SCCs; however, the scnode graph remains acyclic.\n\nWe define a valid PARTITION P of the scnode graph as a mapping from\nscnodes to CLUSTERS such that the projection of the scnode graph using\nmapping P is an acyclic graph.  This third graph is the CLUSTER GRAPH.\n\nEvery partition represents a valid refactoring of the original package\ninto hypothetical subpackages, each cluster being a subpackage.  Two\npartitions define the extreme ends of a spectrum: the MINIMAL partition\nmaps every scnode to a single cluster; it represents the status quo, a\nmonolithic package.  The MAXIMAL partition maps each scnode to a unique\ncluster; this breaks the package up into an impractically large number\nof small fragments.  The ideal partition lies somewhere in between.\n\n\n## Clusters file\n\nThe `--clusters=\u003cfile\u003e` argument specifies a CLUSTERS FILE that constrains\nthe partition algorithm.  The file consists of a number of stanzas, each\nassigning an import path to a cluster (\"mypkg/internal/util\") and\nassigning a set of initial nodes ({x, y, z}) to it:\n\n```\n= mypkg/internal/util\nx\ny  # this is a comment\nz\n```\n\nOrder of stanzas is important: clusters must be be declared bottom to\ntop.  After each stanza, all nodes transitively reachable (via the node\ngraph) from that cluster are assigned to that cluster, if they have not\nyet been assigned to some other cluster.  Thus we need only mention the\nroot nodes of the cluster, not all its internal nodes.  A warning is\nreported if a node mentioned in a stanza already belongs to a previously\ndefined cluster.\n\nThere is an implicit cluster, \"residue\", that holds all remaining nodes\nafter the clusters defined by the file have been processed.  Initially,\nwhen the clusters file is empty, the residue cluster contains the entire\npackage.  (It is logically at the top.)  The task for the user is to\niteratively define new clusters until the residue becomes empty.\n\n\n## Visualization\n\nWhen sockdrawer is run, it analyzes the source package, builds the node\ngraph and the scgraph, loads the clusters file, computes the clusters for\nevery node, and then emits SVG renderings of the three levels of graphs,\nwith nodes colors coded as follows:\n\n```\ngreen = cluster  (candidate subpackage)\npink  = scnode   (strong component of size \u003e 1)\nblue  = node     (func/type/var/const decl)\n```\n\nThe graphs of all clusters, a DAG, has green nodes; clicking one takes\nyou to the graph over scnodes for that cluster, also a DAG.  Each pink\nnode in this graph represents a cyclical bunch of the node graph,\ncollapsed together for ease of viewing.  Each blue node here represents a\nsingleton SCC, a single declaration; singular SCCs are replaced by\ntheir sole element for simplicity.\n\nClicking a pink (plural) scnode shows the cyclical portion of the node\ngraph that it represents.  (If the fusion optimization was enabled, it\nmay not be fully cyclic.)  All of its nodes are blue.\n\nClicking a blue node shows the definition of that node in godoc.\n(The godoc server's base URL is specified by the `--godoc` flag.)\n\n\n## Workflow\n\nInitially, all nodes belong to the \"residue\" cluster.  (GraphViz graph\nrendering can be slow for the first several iterations.  A large monitor\nis essential.)\n\nThe sockdrawer user's task when decomposing a package into clusters is\nto identify the lowest-hanging fruit (so to speak) in the residue\ncluster — a coherent group of related scnodes at the bottom of the\ngraph — and to \"snip off\" a bunch at the \"stem\" by appending a new\nstanza to the clusters file and listing the roots of that bunch in the\nstanza, and then to re-run the tool.\n\n\nNodes may be added to an existing stanza if appropriate, but if they are\nadded to a cluster that is \"too low\", this may create conflicts; keep an\neye out for warnings.\n\nThis process continues iteratively until the residue has become empty\nand the sets of clusters are satisfactory.\n\nThe tool prints the assignments of nodes to clusters: the \"shopping\nlist\" for the refactoring work.  Clusters should be split off into\nsubpackages in dependency order, lowest first.\n\n\n## Caveats\n\nThe analysis chooses a single configuration, such as linux/amd64.\nDeclarations for other configurations (e.g. windows/arm) will be absent\nfrom the node graph.\n\nThere may be some excessively large SCCs in the node graph that reflect\na circularity in the design.  For the purposes of analysis, you can\nbreak them arbitrarily by commenting out some code, though more thought\nwill be required for a principled fix (e.g. dependency injection).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farl%2Fsockdrawer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farl%2Fsockdrawer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farl%2Fsockdrawer/lists"}