Coding Standards¶
Türkçe
Bu sayfa ICOSYS kod tabanından çıkarılmış kodlama standartlarını içerir. Tüm yeni kod bu kurallara uymalıdır. SonarQube Grade A hedeflenir.
General Principles¶
| Principle | Description |
|---|---|
| DRY | No code duplication — extract to methods/constants |
| YAGNI | Don't build for hypothetical future requirements |
| Early Return | Guard clauses instead of deep nesting |
| English Only | All code, JavaDoc, and comments in English |
| Backward Compatibility | Don't break existing APIs or behavior |
Java — Backend Standards¶
Dependency Injection¶
Always use constructor injection. Never use field injection.
// WRONG — Field injection (SonarQube violation)
@Inject
private SomeService service;
// CORRECT — Constructor injection
private final SomeService service;
@Inject // or just single-constructor auto-detection in Spring
public MyClass(SomeService service) {
this.service = service;
}
Türkçe
Spring Boot'ta tek constructor varsa @Inject/@Autowired bile gerekmez —
Spring otomatik algılar. @RequiredArgsConstructor ile Lombok bunu daha da kısaltır.
For controllers, prefer Lombok:
@RestController
@RequestMapping("/api/branch")
@RequiredArgsConstructor
public class BranchController {
private final BranchService service; // auto-injected
}
Controller Pattern¶
@RestController
@RequestMapping("/api/{entity}")
@RequiredArgsConstructor
public class MyEntityController {
private final MyEntityService service;
@GetMapping("/{id}")
public ResponseEntity<MyEntityEditDto> findById(@PathVariable Long id) {
return service.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<MyEntityEditDto> create(@RequestBody MyEntityEditDto dto) {
return ResponseEntity.ok(service.create(dto));
}
@PutMapping("/{id}")
public ResponseEntity<MyEntityEditDto> update(@PathVariable Long id,
@RequestBody MyEntityEditDto dto) {
return ResponseEntity.ok(service.update(id, dto));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
service.delete(id);
return ResponseEntity.noContent().build();
}
@PostMapping("/list")
public ResponseEntity<PageResponse<MyEntityListDto>> findPaged(
@RequestBody PageRequest<MyEntityFilterDto> request) {
return ResponseEntity.ok(service.findPaged(request));
}
@GetMapping("/stats")
public ResponseEntity<EntityStatsDto> getStats() {
return ResponseEntity.ok(service.getStats());
}
// Custom endpoints AFTER standard CRUD
}
Türkçe
Standart CRUD endpoint'leri her controller'da aynı sırayla yazılır:
findById → create → update → delete → findPaged → getStats → özel endpoint'ler.
Service Pattern¶
@Service
@Transactional
@Slf4j
public class MyEntityService extends AbstractCrudService<
MyEntity, Long, MyEntityEditDto, MyEntityListDto, MyEntityFilterDto> {
// 5 abstract methods MUST be implemented
@Override protected ICrudRepository<MyEntity, Long> getRepository() { return repository; }
@Override protected IEditDtoConverter<MyEntity, MyEntityEditDto> getEditConverter() { return editConverter; }
@Override protected IListDtoConverter<MyEntity, MyEntityListDto> getListConverter() { return listConverter; }
@Override protected String getEntityName() { return "MyEntity"; }
@Override protected Long extractId(MyEntity entity) { return entity.getId(); }
// Lifecycle hooks — override as needed
@Override protected void validateForCreate(MyEntityEditDto dto) { }
@Override protected void beforeCreate(MyEntity entity, MyEntityEditDto dto) { }
}
Transaction Rules¶
| Rule | Reason |
|---|---|
@Transactional only on public methods |
Spring proxy cannot intercept private/protected |
Use readOnly = true for queries |
Enables Hibernate optimizations |
Never call a @Transactional method from the same class |
Self-invocation bypasses the proxy |
Self-invocation fix:
// WRONG — self-invocation, @Transactional is bypassed
public void process() {
saveData(); // calls this.saveData(), NOT the proxy
}
@Transactional
public void saveData() { }
// CORRECT — extract shared logic to private helper
private void saveInternal() {
// shared logic here
}
@Transactional
public void process() {
saveInternal(); // private, no proxy needed
}
@Transactional
public void saveData() {
saveInternal();
}
Exception Handling¶
// WRONG
catch (Exception e) {
e.printStackTrace();
}
// CORRECT — specific exception + error code + SLF4J
catch (DataAccessException e) {
log.error("Failed to save entity: {}", e.getMessage(), e);
throw ServiceException.of(ErrorCodes.SYS_DATA_ACCESS_ERROR,
ServiceErrorMsgCodes.ERROR_SAVE_FAILED, entityName);
}
Rules:
- Never catch generic
Exception— use specific exception types - Never use
e.printStackTrace()— use SLF4J - Always use
ErrorCodesconstants — never raw strings in ServiceException - Always include the original exception as cause
Logging¶
// WRONG
System.out.println("Debug: " + value);
log.debug("Value: " + expensiveCall());
// CORRECT
log.debug("REST request to get entity by id: {}", id);
log.error("Operation failed for entity {}: {}", entityName, e.getMessage(), e);
// For expensive operations
if (log.isDebugEnabled()) {
log.debug("Detailed state: {}", computeExpensiveDebugInfo());
}
Null Safety¶
// WRONG
if (obj.getValue() != null) { }
// CORRECT — null-safe chain
if (obj != null && obj.getValue() != null) { }
// BEST — Optional
Optional.ofNullable(obj)
.map(Obj::getValue)
.ifPresent(this::process);
Constants¶
// WRONG — magic strings and numbers
if (retryCount > 3) { }
predicates.add(cb.equal(root.get("crProcess").get("id"), crProcessId));
// CORRECT — named constants
private static final int MAX_RETRY_COUNT = 3;
private static final String FIELD_ID = "id";
private static final String REL_CR_PROCESS = "crProcess";
if (retryCount > MAX_RETRY_COUNT) { }
predicates.add(cb.equal(root.get(REL_CR_PROCESS).get(FIELD_ID), crProcessId));
String Comparison¶
// WRONG
if (status == "ACTIVE") { }
if (str.equals("constant")) { }
// CORRECT — constant on left (null-safe)
if ("ACTIVE".equals(status)) { }
Resource Management¶
// WRONG
InputStream is = new FileInputStream(file);
// ... use is
is.close();
// CORRECT — try-with-resources
try (InputStream is = new FileInputStream(file)) {
// ... use is
}
Cognitive Complexity¶
// WRONG — deeply nested
public void process(Data data) {
if (data != null) {
if (data.isValid()) {
if (data.getType() == Type.A) {
// ...
}
}
}
}
// CORRECT — early return / guard clauses
public void process(Data data) {
if (data == null || !data.isValid()) {
return;
}
if (data.getType() == Type.A) {
processTypeA(data);
return;
}
processDefault(data);
}
Method Parameter Limit¶
SonarQube flags methods with more than 7 parameters. Bundle into a Request DTO:
// WRONG — 10 parameters
public Response upload(MultipartFile file, Long crProcessId, String entityType,
Long entityId, String key, String category, String desc,
String tags, String ip, String userAgent) { }
// CORRECT — Request DTO
public Response upload(MultipartFile file, UploadRequest request,
String ipAddress, String userAgent) { }
Dead Code¶
- Remove unused imports, variables, and methods
- Delete commented-out code immediately — it lives in Git history
- No
// TODOwithout a linked ticket
TypeScript — Frontend Standards¶
Component Structure¶
// Functional component with TypeScript props
interface MyComponentProps {
entityId: number;
onSave: (data: FormData) => void;
}
export function MyComponent({ entityId, onSave }: MyComponentProps) {
// 1. Hooks (useState, useEffect, custom hooks)
const { user } = useAuth();
const [loading, setLoading] = useState(false);
// 2. Derived state (useMemo, useCallback)
const canEdit = useMemo(() => user.hasRole('EDIT'), [user]);
// 3. Event handlers
const handleSubmit = useCallback((data: FormData) => {
onSave(data);
}, [onSave]);
// 4. Render
return <div>...</div>;
}
State Management¶
| Scope | Tool | Example |
|---|---|---|
| Component-local | useState |
Form input values |
| Derived/computed | useMemo / useCallback |
Filtered lists, memoized handlers |
| Global (app-wide) | Zustand store | Auth state, navigation stack |
| Server state | Axios + custom hooks | API data fetching |
// Zustand store pattern
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
login: async (credentials) => { /* ... */ },
logout: () => set({ user: null, isAuthenticated: false }),
}));
API Client Pattern¶
// Centralized Axios instance
const apiClient = axios.create({
baseURL: import.meta.env.VITE_ICGLB_URL,
withCredentials: true,
});
// Response interceptor for global error handling
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// redirect to login
}
return Promise.reject(ApiError.from(error));
}
);
Form Handling¶
// React Hook Form + Zod validation
const schema = z.object({
name: z.string().min(1, 'Required'),
email: z.string().email('Invalid email'),
});
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
Styling¶
// Use cn() utility for conditional classes
import { cn } from '@/shared/utils/cn';
className={cn(
"px-4 py-2 rounded-md",
isActive && "bg-primary-500 text-white",
isDisabled && "opacity-50 cursor-not-allowed"
)}
Türkçe
cn() fonksiyonu clsx + tailwind-merge birleşimidir.
Tailwind class çakışmalarını otomatik çözer (örn. px-2 ile px-4 varsa sadece px-4 kalır).
SonarQube Compliance¶
Target: Grade A on all projects.
Auto-Fix Rules (Fix immediately, no confirmation needed)¶
| Issue | Action |
|---|---|
| Field injection | Convert to constructor injection |
| Missing JavaDoc | Add English JavaDoc to public methods |
| Code smells | Fix immediately |
| Duplicate code | Extract to shared method or constant |
| Security hotspots | Fix immediately |
| Commented-out code | Delete |
| Unused imports/variables | Remove |
SonarQube Configuration¶
| Setting | Value |
|---|---|
| Coverage exclusions | **/* (Entity/DTO jars excluded) |
| Duplication exclusions | **/*Dto.java, **/entities/*.java |
| Scanner plugin | sonar-maven-plugin 4.0–5.0 |