JavaRush /Java Blog /Random-KO /일반적인 프로그래밍 스타일 가이드
pandaFromMinsk
레벨 39
Минск

일반적인 프로그래밍 스타일 가이드

Random-KO 그룹에 게시되었습니다
이 기사는 학술 과정 "Advanced Java"의 일부입니다. 이 과정은 Java 기능을 효과적으로 사용하는 방법을 배우는 데 도움을 주기 위해 고안되었습니다. 이 자료는 객체 생성, 경쟁, 직렬화, 리플렉션 등과 같은 "고급" 주제를 다룹니다. 이 과정에서는 Java 기술을 효과적으로 익히는 방법을 알려줍니다. 자세한 내용은 여기를 참조하세요 .
콘텐츠
1. 소개 2. 변수 범위 3. 클래스 필드 및 지역 변수 4. 메서드 인수 및 지역 변수 5. Boxing 및 Unboxing 6. 인터페이스 7. 문자열 8. 명명 규칙 9. 표준 라이브러리 10. 불변성 11. 테스트 12. 다음. .. 13. 소스 코드 다운로드
1. 소개
튜토리얼의 이 부분에서는 Java의 좋은 프로그래밍 스타일과 반응형 디자인의 일반 원칙에 대한 논의를 계속할 것입니다. 우리는 이미 가이드의 이전 장에서 이러한 원칙 중 일부를 보았지만 Java 개발자의 기술을 향상시키기 위한 많은 실용적인 팁이 제공될 것입니다.
2. 가변 범위
3부("클래스 및 인터페이스를 디자인하는 방법")에서는 범위 제한이 있는 경우 클래스 및 인터페이스 멤버에 가시성과 접근성을 적용하는 방법에 대해 논의했습니다. 그러나 메소드 구현에 사용되는 지역 변수에 대해서는 아직 논의하지 않았습니다. Java 언어에서는 일단 선언된 모든 지역 변수에는 범위가 있습니다. 이 변수는 선언된 위치부터 메서드(또는 코드 블록) 실행이 완료되는 지점까지 표시됩니다. 일반적으로 따라야 할 유일한 규칙은 지역 변수가 사용될 장소에 최대한 가깝게 선언하는 것입니다. 일반적인 예를 살펴보겠습니다. for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } 두 코드 조각 모두에서 변수의 범위는 이러한 변수가 선언된 실행 블록으로 제한됩니다. 블록이 완료되면 범위가 종료되고 변수가 보이지 않게 됩니다. 이것이 더 명확해 보이지만 Java 8이 출시되고 람다가 도입되면서 지역 변수를 사용하는 언어의 잘 알려진 관용구 중 상당수가 더 이상 사용되지 않게 되었습니다. 루프 대신 람다를 사용하는 이전 예제의 예를 들어 보겠습니다. Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); 로컬 변수가 함수에 대한 인수가 된 것을 볼 수 있으며, 이는 차례로 forEach 메서드에 인수로 전달됩니다 .
3. 클래스 필드와 지역 변수
Java의 각 메소드는 특정 클래스(또는 Java8의 경우 해당 메소드가 기본 메소드로 선언된 인터페이스)에 속합니다. 구현에 사용되는 클래스나 메소드의 필드인 지역 변수 사이에는 이름 충돌이 발생할 가능성이 있습니다. Java 컴파일러는 두 명 이상의 개발자가 해당 변수를 사용하려고 하더라도 사용 가능한 변수 중에서 올바른 변수를 선택하는 방법을 알고 있습니다. 최신 Java IDE는 컴파일러 경고 및 변수 강조 표시를 통해 이러한 충돌이 발생할 시기를 개발자에게 알려주는 훌륭한 작업을 수행합니다. 하지만 코드를 작성할 때 그런 것들을 생각하는 것이 여전히 더 좋습니다. 예제를 살펴보는 것이 좋습니다. public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } 예제는 매우 쉬워 보이지만 함정입니다. 계산값 메소드는 지역 변수 값을 도입 하고 이에 대해 작동하여 동일한 이름을 가진 클래스 필드를 숨깁니다. 이 줄은 value += value; 클래스 필드와 지역 변수 값의 합이어야 하지만 대신 다른 작업이 수행되고 있습니다. 적절한 구현은 다음과 같습니다(this 키워드 사용). public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } 이 예제는 어떤 면에서는 단순하지만 경우에 따라 디버깅하고 수정하는 데 몇 시간이 걸릴 수 있다는 중요한 점을 보여줍니다.
4. 메소드 인수 및 지역 변수
경험이 부족한 Java 개발자가 자주 빠지는 또 다른 함정은 메소드 인수를 로컬 변수로 사용하는 것입니다. Java에서는 상수가 아닌 인수에 값을 다시 할당할 수 있습니다(그러나 이는 원래 값에는 영향을 미치지 않습니다): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } 위의 코드 조각은 우아하지는 않지만 문제를 발견하는 데는 효과적입니다. str에는 다른 인수가 할당 됩니다 . value (기본적으로 지역 변수로 사용됩니다) . 모든 경우에(예외 없이) 이 예제 없이도 수행할 수 있고 수행해야 합니다(예: 인수를 상수로 선언). 예를 들면 다음과 같습니다. public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } 이 간단한 규칙을 따르면 지역 변수를 도입할 때에도 주어진 코드를 추적하고 문제의 원인을 찾는 것이 더 쉽습니다.
5. 포장 및 포장 풀기
Boxing 및 unboxing은 기본 유형( int, long, double 등 )을 해당 유형 래퍼( Integer, Long, Double 등) 로 변환하기 위해 Java에서 사용되는 기술의 이름입니다 . 제네릭을 사용하는 방법과 시기 튜토리얼의 4부에서 기본 유형을 제네릭의 유형 매개변수로 래핑하는 방법에 대해 이야기했을 때 이미 이 내용을 확인했습니다. Java 컴파일러는 자동 박싱을 수행하여 이러한 변환을 숨기려고 최선을 다하지만 때로는 이것이 예상보다 적고 예상치 못한 결과를 생성합니다. 예를 살펴보겠습니다. public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); 위의 코드 조각은 정상적으로 컴파일됩니다. 그러나 Longlong 사이를 변환하는 줄에서는 NullPointerException 이 발생합니다 . 그러한 경우에 대한 조언은 기본 유형을 사용하는 것이 바람직하다는 것입니다(그러나 이것이 항상 가능하지는 않다는 것을 이미 알고 있습니다). // блок
6. 인터페이스
튜토리얼의 3부 "클래스 및 인터페이스를 디자인하는 방법"에서는 인터페이스와 계약 프로그래밍에 대해 논의하면서 가능하면 인터페이스가 구체적인 클래스보다 선호되어야 한다는 점을 강조했습니다. 이 섹션의 목적은 실제 예제를 통해 이를 보여줌으로써 인터페이스를 먼저 고려하도록 권장하는 것입니다. 인터페이스는 특정 구현에 연결되지 않습니다(기본 메서드 제외). 이는 단지 계약일 뿐이며, 예를 들어 계약 실행 방식에 있어 많은 자유와 유연성을 제공합니다. 이러한 유연성은 구현에 외부 시스템이나 서비스가 포함될 때 더욱 중요해집니다. 간단한 인터페이스와 가능한 구현의 예를 살펴보겠습니다. public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; } public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } } 위의 코드 조각은 일반적인 인터페이스 패턴과 구현을 보여줍니다. 이 구현은 외부 HTTP 서비스( http://api.genames.org/ )를 사용하여 특정 위치의 시간대를 검색합니다. 그러나 왜냐하면 계약은 인터페이스에 따라 다르므로 데이터베이스나 일반 플랫 파일 등을 사용하여 인터페이스의 다른 구현을 도입하는 것은 매우 쉽습니다. 이를 통해 인터페이스는 테스트 가능한 코드를 설계하는 데 매우 유용합니다. 예를 들어, 모든 테스트에서 외부 서비스를 호출하는 것이 항상 실용적인 것은 아니므로 대신에 대체적이고 간단한 구현(예: 스텁)을 구현하는 것이 합리적입니다. 이 구현은 TimezoneService 인터페이스 가 필요한 public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } 곳 ​​어디에서나 사용할 수 있습니다. 외부 구성 요소에 대한 종속성 테스트 스크립트. 이러한 인터페이스의 효과적인 사용에 대한 많은 훌륭한 예가 Java 표준 라이브러리 내에 캡슐화되어 있습니다. 컬렉션, 목록, 세트 - 이러한 인터페이스에는 원활하게 교체할 수 있고 계약을 활용할 때 상호 교환할 수 있는 여러 구현이 있습니다. 예를 들어: public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } } print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) );
7. 문자열
문자열은 Java 및 기타 프로그래밍 언어에서 가장 많이 사용되는 유형 중 하나입니다. Java 언어는 기본적으로 연결 및 비교 작업을 지원하여 많은 일상적인 문자열 조작을 단순화합니다. 또한 표준 라이브러리에는 문자열 작업을 효율적으로 수행하는 많은 클래스가 포함되어 있습니다. 이것이 바로 이 섹션에서 논의할 내용입니다. Java에서 문자열은 UTF-16 인코딩으로 표현되는 불변 객체입니다. 문자열을 연결할 때마다(또는 원래 문자열을 수정하는 작업을 수행할 때마다) String 클래스의 새 인스턴스가 생성됩니다 . 이로 인해 연결 작업이 매우 비효율적이 되어 String 클래스의 중간 인스턴스가 많이 생성될 수 있습니다 (일반적으로 가비지 생성). 그러나 Java 표준 라이브러리에는 문자열 조작을 편리하게 만드는 것이 목적인 두 개의 매우 유용한 클래스가 포함되어 있습니다. 이들은 StringBuilderStringBuffer 입니다 (이들 사이의 유일한 차이점은 StringBuffer 는 스레드로부터 안전하고 StringBuilder는 그 반대라는 점입니다). 이러한 클래스 중 하나가 사용되는 몇 가지 예를 살펴보겠습니다. StringBuilder/StringBuffer를 final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" ); 사용하는 것이 문자열을 조작하는 데 권장되는 방법이지만 두 개 또는 세 개의 문자열을 연결하는 가장 간단한 시나리오에서는 과도해 보일 수 있으므로 일반 추가 연산자( ("+"), 예: 연결을 단순화하는 가장 좋은 대안은 문자열 형식 지정과 Java 표준 라이브러리를 사용하여 정적 String.format 도우미 메서드를 제공하는 것입니다 . 이는 숫자, 기호, 날짜/시간 등을 포함한 다양한 형식 지정자를 지원합니다. (자세한 내용은 참조 문서를 참조하세요.) String.format 메서드는 다양한 데이터 유형에서 문자열을 생성하는 깔끔하고 가벼운 접근 방식을 제공합니다. 최신 Java IDE는 String.format 메서드 에 전달된 인수에서 형식 사양을 구문 분석 하고 불일치가 감지되면 개발자에게 경고할 수 있다는 점은 주목할 가치가 있습니다. String userId = "user:" + new Random().nextInt( 100 ); String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12%
8. 명명 규칙
Java는 개발자가 명명 규칙을 엄격하게 따르도록 강요하지 않는 언어이지만 커뮤니티에서는 표준 라이브러리와 다른 Java 프로젝트 모두에서 Java 코드가 일관되게 보이도록 하는 간단한 규칙 세트를 개발했습니다.
  • 패키지 이름은 소문자입니다: org.junit, com.fasterxml.jackson, javax.json
  • 클래스 이름, 열거형, 인터페이스, 주석은 대문자로 작성됩니다( StringBuilder, Runnable, @Override).
  • 필드 또는 메소드 이름( static final 제외 )은 낙타 표기법으로 지정됩니다: isEmpty, format, addAll
  • 정적 최종 필드 또는 열거형 상수 이름은 대문자이며 밑줄("_")로 구분됩니다( LOG, MIN_RADIX, INSTANCE).
  • 지역 변수 또는 메소드 인수는 낙타 표기법( str, newLength, maximumCapacity) 으로 입력됩니다.
  • 제네릭의 매개변수 유형 이름은 대문자 T, U, E와 같은 단일 문자로 표시됩니다.
이러한 간단한 규칙을 따르면 여러분이 작성하는 코드는 간결해 보이고 다른 라이브러리나 프레임워크와 스타일이 구별되지 않을 것이며 같은 사람이 개발한 것처럼 느껴질 것입니다(규칙이 실제로 작동하는 드문 경우 중 하나).
9. 표준 라이브러리
어떤 종류의 Java 프로젝트를 진행하든 Java 표준 라이브러리는 가장 친한 친구입니다. 예, 다소 거친 부분과 이상한 디자인 결정이 있다는 점에 동의하기 어렵습니다. 그러나 99%의 경우 전문가가 작성한 고품질 코드입니다. 탐험해 볼 가치가 있습니다. 각 Java 릴리스는 기존 라이브러리에 많은 새로운 기능을 제공하고(이전 기능에 발생할 수 있는 일부 문제 포함) 많은 새 라이브러리도 추가합니다. Java 5는 java.util.concurrent 패키지 의 일부로 새로운 동시성 라이브러리를 가져왔습니다 . Java 6에는 잘 알려지지 않은 스크립팅 지원( javax.script 패키지)과 Java 컴파일러 API ( javax.tools 패키지의 일부 )가 도입되었습니다. Java 7에서는 java.nio.file 패키지 에 새로운 I/O 라이브러리를 도입하고 java.lang.invoke 에서 동적 언어를 지원하는 등 java.util.concurrent 에 많은 개선이 이루어졌습니다 . 그리고 마지막으로 Java 8에서는 오랫동안 기다려온 날짜/시간을 java.time 패키지 에 추가했습니다 . 플랫폼으로서의 Java는 진화하고 있으며, 위의 변화와 함께 발전하는 것이 매우 중요합니다. 프로젝트에 타사 라이브러리나 프레임워크를 포함하는 것을 고려할 때마다 필요한 기능이 표준 Java 라이브러리에 이미 포함되어 있지 않은지 확인하십시오. 알고리즘은 표준 라이브러리에 있지만 대부분의 경우에는 실제로 필요하지 않습니다.
10. 불변성
가이드 전체와 이 부분의 불변성은 상기시켜드리기 위해 남아 있습니다. 진지하게 받아들이시기 바랍니다. 디자인한 클래스나 구현한 메서드가 불변성을 보장할 수 있다면 대부분의 경우 동시에 수정될 염려 없이 어디에서나 사용할 수 있습니다. 이렇게 하면 개발자로서의 삶(그리고 팀원들의 삶)이 더 쉬워질 것입니다.
11. 테스트
테스트 중심 개발(TDD) 관행은 Java 커뮤니티에서 매우 인기가 높으며 코드 품질에 대한 기준을 높입니다. TDD가 제공하는 모든 이점에도 불구하고 오늘날 Java 표준 라이브러리에 테스트 프레임워크나 지원 도구가 포함되어 있지 않다는 것은 슬픈 일입니다. 그러나 테스트는 현대 Java 개발의 필수 부분이 되었으며 이 섹션에서는 JUnit 프레임워크 를 사용하는 몇 가지 기본 기술을 살펴보겠습니다 . JUnit에서 기본적으로 각 테스트는 개체의 예상 상태 또는 동작에 대한 일련의 명령문입니다. 훌륭한 테스트를 작성하는 비결은 한 번에 하나씩 테스트하면서 간단하고 짧게 유지하는 것입니다. 연습으로 String.format이 원하는 결과를 반환하는 문자열 섹션의 함수인지 확인하는 테스트 세트를 작성해 보겠습니다. package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } } 두 테스트 모두 읽기 쉬워 보이며 실행은 인스턴스입니다. 오늘날 평균적인 Java 프로젝트에는 수백 개의 테스트 사례가 포함되어 있어 개발자에게 회귀 또는 기능에 대한 개발 프로세스 중에 빠른 피드백을 제공합니다.
12. 다음
가이드의 이 부분은 Java 프로그래밍 실습과 이 프로그래밍 언어에 대한 매뉴얼과 관련된 일련의 논의를 완료합니다. 다음 시간에는 언어의 기능으로 돌아가서 예외, 유형, 사용 방법 및 시기에 관해 Java의 세계를 탐구하겠습니다.
13. 소스코드 다운로드
이것은 고급 Java 과정의 일반적인 개발 원칙에 대한 강의였습니다. 강의 소스 코드는 여기에서 다운로드할 수 있습니다 .
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION